]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Merge remote-tracking branch 'pochu/error-dialog'
[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,
1869                                 empathy_get_current_action_time (),
1870                                 NULL, NULL);
1871
1872                         g_strfreev (strv);
1873                         return;
1874                 }
1875                 g_strfreev (strv);
1876
1877                 old_window = chat_window_find_chat (chat);
1878                 if (old_window) {
1879                         if (old_window == window) {
1880                                 gtk_drag_finish (context, TRUE, FALSE, time_);
1881                                 return;
1882                         }
1883
1884                         empathy_chat_window_move_chat (old_window, window, chat);
1885                 } else {
1886                         empathy_chat_window_add_chat (window, chat);
1887                 }
1888
1889                 /* Added to take care of any outstanding chat events */
1890                 empathy_chat_window_present_chat (chat,
1891                         TP_USER_ACTION_TIME_NOT_USER_ACTION);
1892
1893                 /* We should return TRUE to remove the data when doing
1894                  * GDK_ACTION_MOVE, but we don't here otherwise it has
1895                  * weird consequences, and we handle that internally
1896                  * anyway with add_chat () and remove_chat ().
1897                  */
1898                 gtk_drag_finish (context, TRUE, FALSE, time_);
1899         }
1900         else if (info == DND_DRAG_TYPE_URI_LIST) {
1901                 EmpathyChatWindowPriv *priv;
1902                 EmpathyContact *contact;
1903                 const gchar *data;
1904
1905                 priv = GET_PRIV (window);
1906                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1907
1908                 /* contact is NULL when current_chat is a multi-user chat.
1909                  * We don't do file transfers to MUCs, so just cancel the drag.
1910                  */
1911                 if (contact == NULL) {
1912                         gtk_drag_finish (context, TRUE, FALSE, time_);
1913                         return;
1914                 }
1915
1916                 data = (const gchar *) gtk_selection_data_get_data (selection);
1917                 empathy_send_file_from_uri_list (contact, data);
1918
1919                 gtk_drag_finish (context, TRUE, FALSE, time_);
1920         }
1921         else if (info == DND_DRAG_TYPE_TAB) {
1922                 EmpathyChat        **chat;
1923                 EmpathyChatWindow   *old_window = NULL;
1924
1925                 DEBUG ("DND tab");
1926
1927                 chat = (void *) gtk_selection_data_get_data (selection);
1928                 old_window = chat_window_find_chat (*chat);
1929
1930                 if (old_window) {
1931                         EmpathyChatWindowPriv *priv;
1932
1933                         priv = GET_PRIV (window);
1934                         priv->dnd_same_window = (old_window == window);
1935                         DEBUG ("DND tab (within same window: %s)",
1936                                 priv->dnd_same_window ? "Yes" : "No");
1937                 }
1938         } else {
1939                 DEBUG ("DND from unknown source");
1940                 gtk_drag_finish (context, FALSE, FALSE, time_);
1941         }
1942 }
1943
1944 static void
1945 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1946                                            guint num_chats_in_manager,
1947                                            EmpathyChatWindow *window)
1948 {
1949         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1950
1951         gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1952                                   num_chats_in_manager > 0);
1953 }
1954
1955 static void
1956 chat_window_finalize (GObject *object)
1957 {
1958         EmpathyChatWindow     *window;
1959         EmpathyChatWindowPriv *priv;
1960
1961         window = EMPATHY_CHAT_WINDOW (object);
1962         priv = GET_PRIV (window);
1963
1964         DEBUG ("Finalized: %p", object);
1965
1966         g_object_unref (priv->ui_manager);
1967         g_object_unref (priv->chatroom_manager);
1968         g_object_unref (priv->notify_mgr);
1969         g_object_unref (priv->gsettings_chat);
1970         g_object_unref (priv->gsettings_notif);
1971         g_object_unref (priv->gsettings_ui);
1972         g_object_unref (priv->sound_mgr);
1973
1974         if (priv->notification != NULL) {
1975                 notify_notification_close (priv->notification, NULL);
1976                 priv->notification = NULL;
1977         }
1978
1979         if (priv->contact_targets) {
1980                 gtk_target_list_unref (priv->contact_targets);
1981         }
1982         if (priv->file_targets) {
1983                 gtk_target_list_unref (priv->file_targets);
1984         }
1985
1986         if (priv->chat_manager) {
1987                 g_signal_handler_disconnect (priv->chat_manager,
1988                                              priv->chat_manager_chats_changed_id);
1989                 g_object_unref (priv->chat_manager);
1990                 priv->chat_manager = NULL;
1991         }
1992
1993         chat_windows = g_list_remove (chat_windows, window);
1994         gtk_widget_destroy (priv->dialog);
1995
1996         G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1997 }
1998
1999 static void
2000 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2001 {
2002         GObjectClass *object_class = G_OBJECT_CLASS (klass);
2003
2004         object_class->finalize = chat_window_finalize;
2005
2006         g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2007 }
2008
2009 static void
2010 empathy_chat_window_init (EmpathyChatWindow *window)
2011 {
2012         GtkBuilder            *gui;
2013         GtkAccelGroup         *accel_group;
2014         GClosure              *closure;
2015         GtkWidget             *menu;
2016         GtkWidget             *submenu;
2017         guint                  i;
2018         GtkWidget             *chat_vbox;
2019         gchar                 *filename;
2020         EmpathySmileyManager  *smiley_manager;
2021         EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2022                 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2023
2024         window->priv = priv;
2025         filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2026         gui = empathy_builder_get_file (filename,
2027                                        "chat_window", &priv->dialog,
2028                                        "chat_vbox", &chat_vbox,
2029                                        "ui_manager", &priv->ui_manager,
2030                                        "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2031                                        "menu_conv_favorite", &priv->menu_conv_favorite,
2032                                        "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2033                                        "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2034                                        "menu_edit_cut", &priv->menu_edit_cut,
2035                                        "menu_edit_copy", &priv->menu_edit_copy,
2036                                        "menu_edit_paste", &priv->menu_edit_paste,
2037                                        "menu_edit_find", &priv->menu_edit_find,
2038                                        "menu_tabs_next", &priv->menu_tabs_next,
2039                                        "menu_tabs_prev", &priv->menu_tabs_prev,
2040                                        "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2041                                        "menu_tabs_left", &priv->menu_tabs_left,
2042                                        "menu_tabs_right", &priv->menu_tabs_right,
2043                                        "menu_tabs_detach", &priv->menu_tabs_detach,
2044                                        NULL);
2045         g_free (filename);
2046
2047         empathy_builder_connect (gui, window,
2048                               "menu_conv", "activate", chat_window_conv_activate_cb,
2049                               "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2050                               "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2051                               "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2052                               "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2053                               "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2054                               "menu_conv_close", "activate", chat_window_close_activate_cb,
2055                               "menu_edit", "activate", chat_window_edit_activate_cb,
2056                               "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2057                               "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2058                               "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2059                               "menu_edit_find", "activate", chat_window_find_activate_cb,
2060                               "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2061                               "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2062                               "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2063                               "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2064                               "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2065                               "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2066                               "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2067                               "menu_help_about", "activate", chat_window_help_about_activate_cb,
2068                               NULL);
2069
2070         g_object_ref (priv->ui_manager);
2071         g_object_unref (gui);
2072
2073         priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2074         priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2075         priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2076         priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2077
2078         priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2079
2080         priv->notebook = gtk_notebook_new ();
2081
2082         g_signal_connect (priv->notebook, "create-window",
2083                 G_CALLBACK (notebook_create_window_cb), window);
2084
2085         gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2086                 "EmpathyChatWindow");
2087         gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2088         gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2089         gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2090         gtk_widget_show (priv->notebook);
2091
2092         /* Set up accels */
2093         accel_group = gtk_accel_group_new ();
2094         gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2095
2096         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2097                 closure =  g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2098                                            window,
2099                                            NULL);
2100                 gtk_accel_group_connect (accel_group,
2101                                          tab_accel_keys[i],
2102                                          GDK_MOD1_MASK,
2103                                          0,
2104                                          closure);
2105         }
2106
2107         g_object_unref (accel_group);
2108
2109         /* Set up drag target lists */
2110         priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2111                                                      G_N_ELEMENTS (drag_types_dest_contact));
2112         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2113                                                   G_N_ELEMENTS (drag_types_dest_file));
2114
2115         /* Set up smiley menu */
2116         smiley_manager = empathy_smiley_manager_dup_singleton ();
2117         submenu = empathy_smiley_menu_new (smiley_manager,
2118                                            chat_window_insert_smiley_activate_cb,
2119                                            window);
2120         menu = gtk_ui_manager_get_widget (priv->ui_manager,
2121                 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2122         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2123         g_object_unref (smiley_manager);
2124
2125         /* Set up signals we can't do with ui file since we may need to
2126          * block/unblock them at some later stage.
2127          */
2128
2129         g_signal_connect (priv->dialog,
2130                           "delete_event",
2131                           G_CALLBACK (chat_window_delete_event_cb),
2132                           window);
2133         g_signal_connect (priv->dialog,
2134                           "focus_in_event",
2135                           G_CALLBACK (chat_window_focus_in_event_cb),
2136                           window);
2137         g_signal_connect_after (priv->notebook,
2138                                 "switch_page",
2139                                 G_CALLBACK (chat_window_page_switched_cb),
2140                                 window);
2141         g_signal_connect (priv->notebook,
2142                           "page_added",
2143                           G_CALLBACK (chat_window_page_added_cb),
2144                           window);
2145         g_signal_connect (priv->notebook,
2146                           "page_removed",
2147                           G_CALLBACK (chat_window_page_removed_cb),
2148                           window);
2149
2150         /* Set up drag and drop */
2151         gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2152                            GTK_DEST_DEFAULT_HIGHLIGHT,
2153                            drag_types_dest,
2154                            G_N_ELEMENTS (drag_types_dest),
2155                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2156
2157         /* connect_after to allow GtkNotebook's built-in tab switching */
2158         g_signal_connect_after (priv->notebook,
2159                                 "drag-motion",
2160                                 G_CALLBACK (chat_window_drag_motion),
2161                                 window);
2162         g_signal_connect (priv->notebook,
2163                           "drag-data-received",
2164                           G_CALLBACK (chat_window_drag_data_received),
2165                           window);
2166         g_signal_connect (priv->notebook,
2167                           "drag-drop",
2168                           G_CALLBACK (chat_window_drag_drop),
2169                           window);
2170
2171         chat_windows = g_list_prepend (chat_windows, window);
2172
2173         /* Set up private details */
2174         priv->chats = NULL;
2175         priv->current_chat = NULL;
2176         priv->notification = NULL;
2177
2178         priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2179
2180         priv->chat_manager = empathy_chat_manager_dup_singleton ();
2181         priv->chat_manager_chats_changed_id =
2182                 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2183                                   G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2184                                   window);
2185
2186         chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2187                                                    empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2188                                                    window);
2189 }
2190
2191 /* Returns the window to open a new tab in if there is a suitable window,
2192  * otherwise, returns NULL indicating that a new window should be added.
2193  */
2194 static EmpathyChatWindow *
2195 empathy_chat_window_get_default (gboolean room)
2196 {
2197         GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2198         GList    *l;
2199         gboolean  separate_windows = TRUE;
2200
2201         separate_windows = g_settings_get_boolean (gsettings,
2202                         EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2203
2204         g_object_unref (gsettings);
2205
2206         if (separate_windows) {
2207                 /* Always create a new window */
2208                 return NULL;
2209         }
2210
2211         for (l = chat_windows; l; l = l->next) {
2212                 EmpathyChatWindow *chat_window;
2213                 guint nb_rooms, nb_private;
2214
2215                 chat_window = l->data;
2216
2217                 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2218
2219                 /* Skip the window if there aren't any rooms in it */
2220                 if (room && nb_rooms == 0)
2221                         continue;
2222
2223                 /* Skip the window if there aren't any 1-1 chats in it */
2224                 if (!room && nb_private == 0)
2225                         continue;
2226
2227                 return chat_window;
2228         }
2229
2230         return NULL;
2231 }
2232
2233 static void
2234 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2235                               EmpathyChat       *chat)
2236 {
2237         EmpathyChatWindowPriv *priv;
2238         GtkWidget             *label;
2239         GtkWidget             *popup_label;
2240         GtkWidget             *child;
2241         GValue                value = { 0, };
2242
2243         g_return_if_fail (window != NULL);
2244         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2245
2246         priv = GET_PRIV (window);
2247
2248         /* Reference the chat object */
2249         g_object_ref (chat);
2250
2251         /* If this window has just been created, position it */
2252         if (priv->chats == NULL) {
2253                 const gchar *name = "chat-window";
2254                 gboolean     separate_windows;
2255
2256                 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2257                                 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2258
2259                 if (empathy_chat_is_room (chat))
2260                         name = "room-window";
2261
2262                 if (separate_windows) {
2263                         gint x, y;
2264
2265                         /* Save current position of the window */
2266                         gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2267
2268                         /* First bind to the 'generic' name. So new window for which we didn't
2269                         * save a geometry yet will have the geometry of the last saved
2270                         * window (bgo #601191). */
2271                         empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2272
2273                         /* Restore previous position of the window so the newly created window
2274                         * won't be in the same position as the latest saved window and so
2275                         * completely hide it. */
2276                         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2277
2278                         /* Then bind it to the name of the contact/room so we'll save the
2279                         * geometry specific to this window */
2280                         name = empathy_chat_get_id (chat);
2281                 }
2282
2283                 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2284         }
2285
2286         child = GTK_WIDGET (chat);
2287         label = chat_window_create_label (window, chat, TRUE);
2288         popup_label = chat_window_create_label (window, chat, FALSE);
2289         gtk_widget_show (child);
2290
2291         g_signal_connect (chat, "notify::name",
2292                           G_CALLBACK (chat_window_chat_notify_cb),
2293                           NULL);
2294         g_signal_connect (chat, "notify::subject",
2295                           G_CALLBACK (chat_window_chat_notify_cb),
2296                           NULL);
2297         g_signal_connect (chat, "notify::remote-contact",
2298                           G_CALLBACK (chat_window_chat_notify_cb),
2299                           NULL);
2300         g_signal_connect (chat, "notify::sms-channel",
2301                           G_CALLBACK (chat_window_chat_notify_cb),
2302                           NULL);
2303         g_signal_connect (chat, "notify::n-messages-sending",
2304                           G_CALLBACK (chat_window_chat_notify_cb),
2305                           NULL);
2306         g_signal_connect (chat, "notify::nb-unread-messages",
2307                           G_CALLBACK (chat_window_chat_notify_cb),
2308                           NULL);
2309         chat_window_chat_notify_cb (chat);
2310
2311         gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2312         gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2313         gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2314         g_value_init (&value, G_TYPE_BOOLEAN);
2315         g_value_set_boolean (&value, TRUE);
2316         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2317                                           child, "tab-expand" , &value);
2318         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2319                                           child,  "tab-fill" , &value);
2320         g_value_unset (&value);
2321
2322         DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2323 }
2324
2325 static void
2326 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2327                                  EmpathyChat       *chat)
2328 {
2329         EmpathyChatWindowPriv *priv;
2330         gint                   position;
2331         EmpathyContact        *remote_contact;
2332         EmpathyChatManager    *chat_manager;
2333
2334         g_return_if_fail (window != NULL);
2335         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2336
2337         priv = GET_PRIV (window);
2338
2339         g_signal_handlers_disconnect_by_func (chat,
2340                                               chat_window_chat_notify_cb,
2341                                               NULL);
2342         remote_contact = g_object_get_data (G_OBJECT (chat),
2343                                             "chat-window-remote-contact");
2344         if (remote_contact) {
2345                 g_signal_handlers_disconnect_by_func (remote_contact,
2346                                                       chat_window_update_chat_tab,
2347                                                       chat);
2348         }
2349
2350         chat_manager = empathy_chat_manager_dup_singleton ();
2351         empathy_chat_manager_closed_chat (chat_manager, chat);
2352         g_object_unref (chat_manager);
2353
2354         position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2355                                           GTK_WIDGET (chat));
2356         gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2357
2358         DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2359
2360         g_object_unref (chat);
2361 }
2362
2363 static void
2364 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2365                                EmpathyChatWindow *new_window,
2366                                EmpathyChat       *chat)
2367 {
2368         GtkWidget *widget;
2369
2370         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2371         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2372         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2373
2374         widget = GTK_WIDGET (chat);
2375
2376         DEBUG ("Chat moving with widget:%p (%d references)", widget,
2377                 G_OBJECT (widget)->ref_count);
2378
2379         /* We reference here to make sure we don't loose the widget
2380          * and the EmpathyChat object during the move.
2381          */
2382         g_object_ref (chat);
2383         g_object_ref (widget);
2384
2385         empathy_chat_window_remove_chat (old_window, chat);
2386         empathy_chat_window_add_chat (new_window, chat);
2387
2388         g_object_unref (widget);
2389         g_object_unref (chat);
2390 }
2391
2392 static void
2393 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2394                                     EmpathyChat       *chat)
2395 {
2396         EmpathyChatWindowPriv *priv;
2397         gint                  page_num;
2398
2399         g_return_if_fail (window != NULL);
2400         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2401
2402         priv = GET_PRIV (window);
2403
2404         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2405                                           GTK_WIDGET (chat));
2406         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2407                                        page_num);
2408 }
2409
2410 EmpathyChat *
2411 empathy_chat_window_find_chat (TpAccount   *account,
2412                                const gchar *id,
2413                                gboolean     sms_channel)
2414 {
2415         GList *l;
2416
2417         g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2418
2419         for (l = chat_windows; l; l = l->next) {
2420                 EmpathyChatWindowPriv *priv;
2421                 EmpathyChatWindow     *window;
2422                 GList                *ll;
2423
2424                 window = l->data;
2425                 priv = GET_PRIV (window);
2426
2427                 for (ll = priv->chats; ll; ll = ll->next) {
2428                         EmpathyChat *chat;
2429
2430                         chat = ll->data;
2431
2432                         if (account == empathy_chat_get_account (chat) &&
2433                             !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2434                             sms_channel == empathy_chat_is_sms_channel (chat)) {
2435                                 return chat;
2436                         }
2437                 }
2438         }
2439
2440         return NULL;
2441 }
2442
2443 void
2444 empathy_chat_window_present_chat (EmpathyChat *chat,
2445                                   gint64 timestamp)
2446 {
2447         EmpathyChatWindow     *window;
2448         EmpathyChatWindowPriv *priv;
2449         guint32 x_timestamp;
2450
2451         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2452
2453         window = chat_window_find_chat (chat);
2454
2455         /* If the chat has no window, create one */
2456         if (window == NULL) {
2457                 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2458                 if (!window) {
2459                         window = empathy_chat_window_new ();
2460
2461                         /* we want to display the newly created window even if we don't present
2462                         * it */
2463                         priv = GET_PRIV (window);
2464                         gtk_widget_show (priv->dialog);
2465                 }
2466
2467                 empathy_chat_window_add_chat (window, chat);
2468         }
2469
2470         /* Don't force the window to show itself when it wasn't
2471          * an action by the user
2472          */
2473         if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2474                 return;
2475
2476         priv = GET_PRIV (window);
2477
2478         if (x_timestamp != GDK_CURRENT_TIME) {
2479                 /* Don't present or switch tab if the action was earlier than the
2480                  * last actions X time, accounting for overflow and the first ever
2481                 * presentation */
2482
2483                 if (priv->x_user_action_time != 0
2484                         && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2485                         return;
2486
2487                 priv->x_user_action_time = x_timestamp;
2488         }
2489
2490         empathy_chat_window_switch_to_chat (window, chat);
2491         empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2492           x_timestamp);
2493
2494         gtk_widget_grab_focus (chat->input_text_view);
2495 }
2496
2497 static void
2498 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2499                                guint *nb_rooms,
2500                                guint *nb_private)
2501 {
2502         EmpathyChatWindowPriv *priv = GET_PRIV (self);
2503         GList *l;
2504         guint _nb_rooms = 0, _nb_private = 0;
2505
2506         for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2507                 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2508                         _nb_rooms++;
2509                 else
2510                         _nb_private++;
2511         }
2512
2513         if (nb_rooms != NULL)
2514                 *nb_rooms = _nb_rooms;
2515         if (nb_private != NULL)
2516                 *nb_private = _nb_private;
2517 }