]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Fixed headers in galician translation file
[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);
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_name (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                               GtkNotebookPage  *page,
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         child = gtk_notebook_get_nth_page (notebook, page_num);
1477         chat = EMPATHY_CHAT (child);
1478
1479         if (priv->page_added) {
1480                 priv->page_added = FALSE;
1481                 empathy_chat_scroll_down (chat);
1482         }
1483         else if (priv->current_chat == chat) {
1484                 return;
1485         }
1486
1487         priv->current_chat = chat;
1488         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1489         empathy_chat_messages_read (chat);
1490
1491         chat_window_update_chat_tab (chat);
1492 }
1493
1494 static void
1495 chat_window_page_added_cb (GtkNotebook      *notebook,
1496                            GtkWidget        *child,
1497                            guint             page_num,
1498                            EmpathyChatWindow *window)
1499 {
1500         EmpathyChatWindowPriv *priv;
1501         EmpathyChat           *chat;
1502
1503         priv = GET_PRIV (window);
1504
1505         /* If we just received DND to the same window, we don't want
1506          * to do anything here like removing the tab and then readding
1507          * it, so we return here and in "page-added".
1508          */
1509         if (priv->dnd_same_window) {
1510                 DEBUG ("Page added (back to the same window)");
1511                 priv->dnd_same_window = FALSE;
1512                 return;
1513         }
1514
1515         DEBUG ("Page added");
1516
1517         /* Get chat object */
1518         chat = EMPATHY_CHAT (child);
1519
1520         /* Connect chat signals for this window */
1521         g_signal_connect (chat, "composing",
1522                           G_CALLBACK (chat_window_composing_cb),
1523                           window);
1524         g_signal_connect (chat, "new-message",
1525                           G_CALLBACK (chat_window_new_message_cb),
1526                           window);
1527         g_signal_connect (chat, "notify::tp-chat",
1528                           G_CALLBACK (chat_window_update_chat_tab),
1529                           window);
1530
1531         /* Set flag so we know to perform some special operations on
1532          * switch page due to the new page being added.
1533          */
1534         priv->page_added = TRUE;
1535
1536         /* Get list of chats up to date */
1537         priv->chats = g_list_append (priv->chats, chat);
1538
1539         chat_window_update_chat_tab (chat);
1540 }
1541
1542 static void
1543 chat_window_page_removed_cb (GtkNotebook      *notebook,
1544                              GtkWidget        *child,
1545                              guint             page_num,
1546                              EmpathyChatWindow *window)
1547 {
1548         EmpathyChatWindowPriv *priv;
1549         EmpathyChat           *chat;
1550
1551         priv = GET_PRIV (window);
1552
1553         /* If we just received DND to the same window, we don't want
1554          * to do anything here like removing the tab and then readding
1555          * it, so we return here and in "page-added".
1556          */
1557         if (priv->dnd_same_window) {
1558                 DEBUG ("Page removed (and will be readded to same window)");
1559                 return;
1560         }
1561
1562         DEBUG ("Page removed");
1563
1564         /* Get chat object */
1565         chat = EMPATHY_CHAT (child);
1566
1567         /* Disconnect all signal handlers for this chat and this window */
1568         g_signal_handlers_disconnect_by_func (chat,
1569                                               G_CALLBACK (chat_window_composing_cb),
1570                                               window);
1571         g_signal_handlers_disconnect_by_func (chat,
1572                                               G_CALLBACK (chat_window_new_message_cb),
1573                                               window);
1574         g_signal_handlers_disconnect_by_func (chat,
1575                                               G_CALLBACK (chat_window_update_chat_tab),
1576                                               window);
1577
1578         /* Keep list of chats up to date */
1579         priv->chats = g_list_remove (priv->chats, chat);
1580         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1581         empathy_chat_messages_read (chat);
1582         priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1583
1584         if (priv->chats == NULL) {
1585                 g_object_unref (window);
1586         } else {
1587                 chat_window_update (window);
1588         }
1589 }
1590
1591 static gboolean
1592 chat_window_focus_in_event_cb (GtkWidget        *widget,
1593                                GdkEvent         *event,
1594                                EmpathyChatWindow *window)
1595 {
1596         EmpathyChatWindowPriv *priv;
1597
1598         priv = GET_PRIV (window);
1599
1600         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
1601         empathy_chat_messages_read (priv->current_chat);
1602
1603         chat_window_set_urgency_hint (window, FALSE);
1604
1605         /* Update the title, since we now mark all unread messages as read. */
1606         chat_window_update_chat_tab (priv->current_chat);
1607
1608         return FALSE;
1609 }
1610
1611 static gboolean
1612 chat_window_drag_drop (GtkWidget        *widget,
1613                          GdkDragContext   *context,
1614                          int               x,
1615                          int               y,
1616                          guint             time_,
1617                          EmpathyChatWindow *window)
1618 {
1619         GdkAtom target;
1620         EmpathyChatWindowPriv *priv;
1621
1622         priv = GET_PRIV (window);
1623
1624         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1625         if (target == GDK_NONE)
1626                 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1627
1628         if (target != GDK_NONE) {
1629                 gtk_drag_get_data (widget, context, target, time_);
1630                 return TRUE;
1631         }
1632
1633         return FALSE;
1634 }
1635
1636 static gboolean
1637 chat_window_drag_motion (GtkWidget        *widget,
1638                          GdkDragContext   *context,
1639                          int               x,
1640                          int               y,
1641                          guint             time_,
1642                          EmpathyChatWindow *window)
1643 {
1644         GdkAtom target;
1645         EmpathyChatWindowPriv *priv;
1646
1647         priv = GET_PRIV (window);
1648
1649         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1650         if (target != GDK_NONE) {
1651                 /* This is a file drag.  Ensure the contact is online and set the
1652                    drag type to COPY.  Note that it's possible that the tab will
1653                    be switched by GTK+ after a timeout from drag_motion without
1654                    getting another drag_motion to disable the drop.  You have
1655                    to hold your mouse really still.
1656                  */
1657                 EmpathyContact *contact;
1658
1659                 priv = GET_PRIV (window);
1660                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1661                 /* contact is NULL for multi-user chats.  We don't do
1662                  * file transfers to MUCs.  We also don't send files
1663                  * to offline contacts or contacts that don't support
1664                  * file transfer.
1665                  */
1666                 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1667                         gdk_drag_status (context, 0, time_);
1668                         return FALSE;
1669                 }
1670                 if (!(empathy_contact_get_capabilities (contact)
1671                            & EMPATHY_CAPABILITIES_FT)) {
1672                         gdk_drag_status (context, 0, time_);
1673                         return FALSE;
1674                 }
1675                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1676                 return TRUE;
1677         }
1678
1679         target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1680         if (target != GDK_NONE) {
1681                 /* This is a drag of a contact from a contact list.  Set to COPY.
1682                    FIXME: If this drag is to a MUC window, it invites the user.
1683                    Otherwise, it opens a chat.  Should we use a different drag
1684                    type for invites?  Should we allow ASK?
1685                  */
1686                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1687                 return TRUE;
1688         }
1689
1690         return FALSE;
1691 }
1692
1693 static void
1694 chat_window_drag_data_received (GtkWidget        *widget,
1695                                 GdkDragContext   *context,
1696                                 int               x,
1697                                 int               y,
1698                                 GtkSelectionData *selection,
1699                                 guint             info,
1700                                 guint             time_,
1701                                 EmpathyChatWindow *window)
1702 {
1703         if (info == DND_DRAG_TYPE_CONTACT_ID) {
1704                 EmpathyChat           *chat = NULL;
1705                 EmpathyChatWindow     *old_window;
1706                 TpAccount             *account = NULL;
1707                 TpAccountManager      *account_manager;
1708                 const gchar           *id;
1709                 gchar                **strv;
1710                 const gchar           *account_id;
1711                 const gchar           *contact_id;
1712
1713                 id = (const gchar*) gtk_selection_data_get_data (selection);
1714
1715                 /* FIXME: Perhaps should be sure that the account manager is
1716                  * prepared before calling _ensure_account on it. */
1717                 account_manager = tp_account_manager_dup ();
1718
1719                 DEBUG ("DND contact from roster with id:'%s'", id);
1720
1721                 strv = g_strsplit (id, ":", 2);
1722                 if (g_strv_length (strv) == 2) {
1723                         account_id = strv[0];
1724                         contact_id = strv[1];
1725                         account =
1726                                 tp_account_manager_ensure_account (account_manager, account_id);
1727                         if (account != NULL)
1728                                 chat = empathy_chat_window_find_chat (account, contact_id);
1729                 }
1730
1731                 if (account == NULL) {
1732                         g_strfreev (strv);
1733                         gtk_drag_finish (context, FALSE, FALSE, time_);
1734                         return;
1735                 }
1736
1737                 if (!chat) {
1738                         TpConnection *connection;
1739
1740                         connection = tp_account_get_connection (account);
1741
1742                         if (connection) {
1743                                 empathy_dispatcher_chat_with_contact_id (
1744                                         connection, contact_id, gtk_get_current_event_time (), NULL, NULL);
1745                         }
1746
1747                         g_strfreev (strv);
1748                         return;
1749                 }
1750                 g_object_unref (account_manager);
1751                 g_strfreev (strv);
1752
1753                 old_window = chat_window_find_chat (chat);
1754                 if (old_window) {
1755                         if (old_window == window) {
1756                                 gtk_drag_finish (context, TRUE, FALSE, time_);
1757                                 return;
1758                         }
1759
1760                         empathy_chat_window_move_chat (old_window, window, chat);
1761                 } else {
1762                         empathy_chat_window_add_chat (window, chat);
1763                 }
1764
1765                 /* Added to take care of any outstanding chat events */
1766                 empathy_chat_window_present_chat (chat,
1767                         EMPATHY_DISPATCHER_NON_USER_ACTION);
1768
1769                 /* We should return TRUE to remove the data when doing
1770                  * GDK_ACTION_MOVE, but we don't here otherwise it has
1771                  * weird consequences, and we handle that internally
1772                  * anyway with add_chat () and remove_chat ().
1773                  */
1774                 gtk_drag_finish (context, TRUE, FALSE, time_);
1775         }
1776         else if (info == DND_DRAG_TYPE_URI_LIST) {
1777                 EmpathyChatWindowPriv *priv;
1778                 EmpathyContact *contact;
1779                 const gchar *data;
1780
1781                 priv = GET_PRIV (window);
1782                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1783
1784                 /* contact is NULL when current_chat is a multi-user chat.
1785                  * We don't do file transfers to MUCs, so just cancel the drag.
1786                  */
1787                 if (contact == NULL) {
1788                         gtk_drag_finish (context, TRUE, FALSE, time_);
1789                         return;
1790                 }
1791
1792                 data = (const gchar *) gtk_selection_data_get_data (selection);
1793                 empathy_send_file_from_uri_list (contact, data);
1794
1795                 gtk_drag_finish (context, TRUE, FALSE, time_);
1796         }
1797         else if (info == DND_DRAG_TYPE_TAB) {
1798                 EmpathyChat        **chat;
1799                 EmpathyChatWindow   *old_window = NULL;
1800
1801                 DEBUG ("DND tab");
1802
1803                 chat = (void *) gtk_selection_data_get_data (selection);
1804                 old_window = chat_window_find_chat (*chat);
1805
1806                 if (old_window) {
1807                         EmpathyChatWindowPriv *priv;
1808
1809                         priv = GET_PRIV (window);
1810                         priv->dnd_same_window = (old_window == window);
1811                         DEBUG ("DND tab (within same window: %s)",
1812                                 priv->dnd_same_window ? "Yes" : "No");
1813                 }
1814         } else {
1815                 DEBUG ("DND from unknown source");
1816                 gtk_drag_finish (context, FALSE, FALSE, time_);
1817         }
1818 }
1819
1820 static void
1821 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1822                                            guint num_chats_in_manager,
1823                                            EmpathyChatWindow *window)
1824 {
1825         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1826
1827         gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1828                                   num_chats_in_manager > 0);
1829 }
1830
1831 static void
1832 chat_window_finalize (GObject *object)
1833 {
1834         EmpathyChatWindow     *window;
1835         EmpathyChatWindowPriv *priv;
1836
1837         window = EMPATHY_CHAT_WINDOW (object);
1838         priv = GET_PRIV (window);
1839
1840         DEBUG ("Finalized: %p", object);
1841
1842         g_object_unref (priv->ui_manager);
1843         g_object_unref (priv->chatroom_manager);
1844         g_object_unref (priv->notify_mgr);
1845
1846         if (priv->notification != NULL) {
1847                 notify_notification_close (priv->notification, NULL);
1848                 priv->notification = NULL;
1849         }
1850
1851         if (priv->contact_targets) {
1852                 gtk_target_list_unref (priv->contact_targets);
1853         }
1854         if (priv->file_targets) {
1855                 gtk_target_list_unref (priv->file_targets);
1856         }
1857
1858         if (priv->chat_manager) {
1859                 g_signal_handler_disconnect (priv->chat_manager,
1860                                              priv->chat_manager_chats_changed_id);
1861                 g_object_unref (priv->chat_manager);
1862                 priv->chat_manager = NULL;
1863         }
1864
1865         chat_windows = g_list_remove (chat_windows, window);
1866         gtk_widget_destroy (priv->dialog);
1867
1868         G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1869 }
1870
1871 static void
1872 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1873 {
1874         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1875
1876         object_class->finalize = chat_window_finalize;
1877
1878         g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1879
1880         /* Set up a style for the close button with no focus padding. */
1881         gtk_rc_parse_string (
1882                 "style \"empathy-close-button-style\"\n"
1883                 "{\n"
1884                 "  GtkWidget::focus-padding = 0\n"
1885                 "  xthickness = 0\n"
1886                 "  ythickness = 0\n"
1887                 "}\n"
1888                 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1889
1890         gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1891 }
1892
1893 static void
1894 empathy_chat_window_init (EmpathyChatWindow *window)
1895 {
1896         GtkBuilder            *gui;
1897         GtkAccelGroup         *accel_group;
1898         GClosure              *closure;
1899         GtkWidget             *menu;
1900         GtkWidget             *submenu;
1901         guint                  i;
1902         GtkWidget             *chat_vbox;
1903         gchar                 *filename;
1904         EmpathySmileyManager  *smiley_manager;
1905         EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1906                 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1907
1908         window->priv = priv;
1909         filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1910         gui = empathy_builder_get_file (filename,
1911                                        "chat_window", &priv->dialog,
1912                                        "chat_vbox", &chat_vbox,
1913                                        "ui_manager", &priv->ui_manager,
1914                                        "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1915                                        "menu_conv_favorite", &priv->menu_conv_favorite,
1916                                        "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
1917                                        "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1918                                        "menu_edit_cut", &priv->menu_edit_cut,
1919                                        "menu_edit_copy", &priv->menu_edit_copy,
1920                                        "menu_edit_paste", &priv->menu_edit_paste,
1921                                        "menu_edit_find", &priv->menu_edit_find,
1922                                        "menu_tabs_next", &priv->menu_tabs_next,
1923                                        "menu_tabs_prev", &priv->menu_tabs_prev,
1924                                        "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1925                                        "menu_tabs_left", &priv->menu_tabs_left,
1926                                        "menu_tabs_right", &priv->menu_tabs_right,
1927                                        "menu_tabs_detach", &priv->menu_tabs_detach,
1928                                        NULL);
1929         g_free (filename);
1930
1931         empathy_builder_connect (gui, window,
1932                               "menu_conv", "activate", chat_window_conv_activate_cb,
1933                               "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1934                               "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1935                               "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
1936                               "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1937                               "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1938                               "menu_conv_close", "activate", chat_window_close_activate_cb,
1939                               "menu_edit", "activate", chat_window_edit_activate_cb,
1940                               "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1941                               "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1942                               "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1943                               "menu_edit_find", "activate", chat_window_find_activate_cb,
1944                               "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1945                               "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1946                               "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1947                               "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1948                               "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1949                               "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1950                               "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1951                               "menu_help_about", "activate", chat_window_help_about_activate_cb,
1952                               NULL);
1953
1954         g_object_ref (priv->ui_manager);
1955         g_object_unref (gui);
1956
1957         priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1958
1959         priv->notebook = gtk_notebook_new ();
1960         gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
1961         gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
1962         gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
1963         gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1964         gtk_widget_show (priv->notebook);
1965
1966         /* Set up accels */
1967         accel_group = gtk_accel_group_new ();
1968         gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
1969
1970         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
1971                 closure =  g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
1972                                            window,
1973                                            NULL);
1974                 gtk_accel_group_connect (accel_group,
1975                                          tab_accel_keys[i],
1976                                          GDK_MOD1_MASK,
1977                                          0,
1978                                          closure);
1979         }
1980
1981         g_object_unref (accel_group);
1982
1983         /* Set up drag target lists */
1984         priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
1985                                                      G_N_ELEMENTS (drag_types_dest_contact));
1986         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1987                                                   G_N_ELEMENTS (drag_types_dest_file));
1988
1989         /* Set up smiley menu */
1990         smiley_manager = empathy_smiley_manager_dup_singleton ();
1991         submenu = empathy_smiley_menu_new (smiley_manager,
1992                                            chat_window_insert_smiley_activate_cb,
1993                                            window);
1994         menu = gtk_ui_manager_get_widget (priv->ui_manager,
1995                 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
1996         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
1997         g_object_unref (smiley_manager);
1998
1999         /* Set up signals we can't do with ui file since we may need to
2000          * block/unblock them at some later stage.
2001          */
2002
2003         g_signal_connect (priv->dialog,
2004                           "delete_event",
2005                           G_CALLBACK (chat_window_delete_event_cb),
2006                           window);
2007         g_signal_connect (priv->dialog,
2008                           "focus_in_event",
2009                           G_CALLBACK (chat_window_focus_in_event_cb),
2010                           window);
2011         g_signal_connect_after (priv->notebook,
2012                                 "switch_page",
2013                                 G_CALLBACK (chat_window_page_switched_cb),
2014                                 window);
2015         g_signal_connect (priv->notebook,
2016                           "page_added",
2017                           G_CALLBACK (chat_window_page_added_cb),
2018                           window);
2019         g_signal_connect (priv->notebook,
2020                           "page_removed",
2021                           G_CALLBACK (chat_window_page_removed_cb),
2022                           window);
2023
2024         /* Set up drag and drop */
2025         gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2026                            GTK_DEST_DEFAULT_HIGHLIGHT,
2027                            drag_types_dest,
2028                            G_N_ELEMENTS (drag_types_dest),
2029                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2030
2031         /* connect_after to allow GtkNotebook's built-in tab switching */
2032         g_signal_connect_after (priv->notebook,
2033                                 "drag-motion",
2034                                 G_CALLBACK (chat_window_drag_motion),
2035                                 window);
2036         g_signal_connect (priv->notebook,
2037                           "drag-data-received",
2038                           G_CALLBACK (chat_window_drag_data_received),
2039                           window);
2040         g_signal_connect (priv->notebook,
2041                           "drag-drop",
2042                           G_CALLBACK (chat_window_drag_drop),
2043                           window);
2044
2045         chat_windows = g_list_prepend (chat_windows, window);
2046
2047         /* Set up private details */
2048         priv->chats = NULL;
2049         priv->chats_new_msg = NULL;
2050         priv->chats_composing = NULL;
2051         priv->current_chat = NULL;
2052         priv->notification = NULL;
2053
2054         priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2055
2056         priv->chat_manager = empathy_chat_manager_dup_singleton ();
2057         priv->chat_manager_chats_changed_id =
2058                 g_signal_connect (priv->chat_manager, "chats-changed",
2059                                   G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2060                                   window);
2061
2062         chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2063                                                    empathy_chat_manager_get_num_chats (priv->chat_manager),
2064                                                    window);
2065 }
2066
2067 EmpathyChatWindow *
2068 empathy_chat_window_new (void)
2069 {
2070         return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
2071 }
2072
2073 /* Returns the window to open a new tab in if there is only one window
2074  * visble, otherwise, returns NULL indicating that a new window should
2075  * be added.
2076  */
2077 EmpathyChatWindow *
2078 empathy_chat_window_get_default (gboolean room)
2079 {
2080         GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2081         GList    *l;
2082         gboolean  separate_windows = TRUE;
2083
2084         separate_windows = g_settings_get_boolean (gsettings,
2085                         EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2086
2087         g_object_unref (gsettings);
2088
2089         if (separate_windows) {
2090                 /* Always create a new window */
2091                 return NULL;
2092         }
2093
2094         for (l = chat_windows; l; l = l->next) {
2095                 EmpathyChatWindowPriv *priv;
2096                 EmpathyChatWindow *chat_window;
2097                 GtkWidget         *dialog;
2098
2099                 chat_window = l->data;
2100                 priv = GET_PRIV (chat_window);
2101
2102                 dialog = empathy_chat_window_get_dialog (chat_window);
2103                 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
2104                         guint nb_rooms, nb_private;
2105                         empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2106
2107                         /* Skip the window if there aren't any rooms in it */
2108                         if (room && nb_rooms == 0)
2109                                 continue;
2110
2111                         /* Skip the window if there aren't any 1-1 chats in it */
2112                         if (!room && nb_private == 0)
2113                                 continue;
2114
2115                         /* Found a visible window on this desktop */
2116                         return chat_window;
2117                 }
2118         }
2119
2120         return NULL;
2121 }
2122
2123 GtkWidget *
2124 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2125 {
2126         EmpathyChatWindowPriv *priv;
2127
2128         g_return_val_if_fail (window != NULL, NULL);
2129
2130         priv = GET_PRIV (window);
2131
2132         return priv->dialog;
2133 }
2134
2135 void
2136 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2137                               EmpathyChat       *chat)
2138 {
2139         EmpathyChatWindowPriv *priv;
2140         GtkWidget             *label;
2141         GtkWidget             *popup_label;
2142         GtkWidget             *child;
2143         GValue                value = { 0, };
2144
2145         g_return_if_fail (window != NULL);
2146         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2147
2148         priv = GET_PRIV (window);
2149
2150         /* Reference the chat object */
2151         g_object_ref (chat);
2152
2153         /* If this window has just been created, position it */
2154         if (priv->chats == NULL) {
2155                 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2156                 const gchar *name = "chat-window";
2157                 gboolean     separate_windows;
2158
2159                 separate_windows = g_settings_get_boolean (gsettings,
2160                                 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2161
2162                 g_object_unref (gsettings);
2163
2164                 if (separate_windows) {
2165                         name = empathy_chat_get_id (chat);
2166                 }
2167                 else if (empathy_chat_is_room (chat)) {
2168                         name = "room-window";
2169                 }
2170
2171                 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2172         }
2173
2174         child = GTK_WIDGET (chat);
2175         label = chat_window_create_label (window, chat, TRUE);
2176         popup_label = chat_window_create_label (window, chat, FALSE);
2177         gtk_widget_show (child);
2178
2179         g_signal_connect (chat, "notify::name",
2180                           G_CALLBACK (chat_window_chat_notify_cb),
2181                           NULL);
2182         g_signal_connect (chat, "notify::subject",
2183                           G_CALLBACK (chat_window_chat_notify_cb),
2184                           NULL);
2185         g_signal_connect (chat, "notify::remote-contact",
2186                           G_CALLBACK (chat_window_chat_notify_cb),
2187                           NULL);
2188         chat_window_chat_notify_cb (chat);
2189
2190         gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2191         gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2192         gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2193         g_value_init (&value, G_TYPE_BOOLEAN);
2194         g_value_set_boolean (&value, TRUE);
2195         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2196                                           child, "tab-expand" , &value);
2197         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2198                                           child,  "tab-fill" , &value);
2199         g_value_unset (&value);
2200
2201         DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2202 }
2203
2204 void
2205 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2206                                  EmpathyChat       *chat)
2207 {
2208         EmpathyChatWindowPriv *priv;
2209         gint                   position;
2210         EmpathyContact        *remote_contact;
2211         EmpathyChatManager    *chat_manager;
2212
2213         g_return_if_fail (window != NULL);
2214         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2215
2216         priv = GET_PRIV (window);
2217
2218         g_signal_handlers_disconnect_by_func (chat,
2219                                               chat_window_chat_notify_cb,
2220                                               NULL);
2221         remote_contact = g_object_get_data (G_OBJECT (chat),
2222                                             "chat-window-remote-contact");
2223         if (remote_contact) {
2224                 g_signal_handlers_disconnect_by_func (remote_contact,
2225                                                       chat_window_update_chat_tab,
2226                                                       chat);
2227         }
2228
2229         chat_manager = empathy_chat_manager_dup_singleton ();
2230         empathy_chat_manager_closed_chat (chat_manager, chat);
2231         g_object_unref (chat_manager);
2232
2233         position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2234                                           GTK_WIDGET (chat));
2235         gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2236
2237         DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2238
2239         g_object_unref (chat);
2240 }
2241
2242 void
2243 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2244                                EmpathyChatWindow *new_window,
2245                                EmpathyChat       *chat)
2246 {
2247         GtkWidget *widget;
2248
2249         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2250         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2251         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2252
2253         widget = GTK_WIDGET (chat);
2254
2255         DEBUG ("Chat moving with widget:%p (%d references)", widget,
2256                 G_OBJECT (widget)->ref_count);
2257
2258         /* We reference here to make sure we don't loose the widget
2259          * and the EmpathyChat object during the move.
2260          */
2261         g_object_ref (chat);
2262         g_object_ref (widget);
2263
2264         empathy_chat_window_remove_chat (old_window, chat);
2265         empathy_chat_window_add_chat (new_window, chat);
2266
2267         g_object_unref (widget);
2268         g_object_unref (chat);
2269 }
2270
2271 void
2272 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2273                                     EmpathyChat       *chat)
2274 {
2275         EmpathyChatWindowPriv *priv;
2276         gint                  page_num;
2277
2278         g_return_if_fail (window != NULL);
2279         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2280
2281         priv = GET_PRIV (window);
2282
2283         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2284                                           GTK_WIDGET (chat));
2285         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2286                                        page_num);
2287 }
2288
2289 gboolean
2290 empathy_chat_window_has_focus (EmpathyChatWindow *window)
2291 {
2292         EmpathyChatWindowPriv *priv;
2293         gboolean              has_focus;
2294
2295         g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
2296
2297         priv = GET_PRIV (window);
2298
2299         g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
2300
2301         return has_focus;
2302 }
2303
2304 EmpathyChat *
2305 empathy_chat_window_find_chat (TpAccount   *account,
2306                                const gchar *id)
2307 {
2308         GList *l;
2309
2310         g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2311
2312         for (l = chat_windows; l; l = l->next) {
2313                 EmpathyChatWindowPriv *priv;
2314                 EmpathyChatWindow     *window;
2315                 GList                *ll;
2316
2317                 window = l->data;
2318                 priv = GET_PRIV (window);
2319
2320                 for (ll = priv->chats; ll; ll = ll->next) {
2321                         EmpathyChat *chat;
2322
2323                         chat = ll->data;
2324
2325                         if (account == empathy_chat_get_account (chat) &&
2326                             !tp_strdiff (id, empathy_chat_get_id (chat))) {
2327                                 return chat;
2328                         }
2329                 }
2330         }
2331
2332         return NULL;
2333 }
2334
2335 void
2336 empathy_chat_window_present_chat (EmpathyChat *chat,
2337                                   gint64 timestamp)
2338 {
2339         EmpathyChatWindow     *window;
2340         EmpathyChatWindowPriv *priv;
2341         guint32 x_timestamp;
2342
2343         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2344
2345         window = chat_window_find_chat (chat);
2346
2347         /* If the chat has no window, create one */
2348         if (window == NULL) {
2349                 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2350                 if (!window) {
2351                         window = empathy_chat_window_new ();
2352                         gtk_widget_show_all (GET_PRIV (window)->dialog);
2353                 }
2354
2355                 empathy_chat_window_add_chat (window, chat);
2356         }
2357
2358         /* Don't force the window to show itself when it wasn't
2359          * an action by the user
2360          */
2361         if (timestamp == EMPATHY_DISPATCHER_NON_USER_ACTION)
2362                 return;
2363
2364         priv = GET_PRIV (window);
2365
2366         if (timestamp == EMPATHY_DISPATCHER_CURRENT_TIME) {
2367                 x_timestamp = GDK_CURRENT_TIME;
2368         } else {
2369                 x_timestamp = CLAMP (timestamp, 0, G_MAXUINT32);
2370                 /* Don't present or switch tab if the action was earlier than the
2371                  * last actions X time, accounting for overflow and the first ever
2372                 * presentation */
2373
2374                 if (priv->x_user_action_time != 0
2375                         && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2376                         return;
2377
2378                 priv->x_user_action_time = x_timestamp;
2379         }
2380
2381         empathy_chat_window_switch_to_chat (window, chat);
2382         empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2383           x_timestamp);
2384
2385         gtk_widget_grab_focus (chat->input_text_view);
2386 }
2387
2388 void
2389 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2390                                guint *nb_rooms,
2391                                guint *nb_private)
2392 {
2393         EmpathyChatWindowPriv *priv = GET_PRIV (self);
2394         GList *l;
2395         guint _nb_rooms = 0, _nb_private = 0;
2396
2397         for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2398                 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2399                         _nb_rooms++;
2400                 else
2401                         _nb_private++;
2402         }
2403
2404         if (nb_rooms != NULL)
2405                 *nb_rooms = _nb_rooms;
2406         if (nb_private != NULL)
2407                 *nb_private = _nb_private;
2408 }