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