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