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