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