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