]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Port to new tp-glib client factory
[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
1380         pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1381                 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1382
1383         if (pixbuf != NULL) {
1384                 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1385                 g_object_unref (pixbuf);
1386         }
1387
1388         notify_notification_show (notification, NULL);
1389
1390         g_free (escaped);
1391 }
1392
1393 static void
1394 chat_window_set_highlight_room_labels (EmpathyChat *chat)
1395 {
1396         gchar *markup, *name;
1397         GtkWidget *widget;
1398
1399         if (!empathy_chat_is_room (chat))
1400                 return;
1401
1402         name = empathy_chat_dup_name (chat);
1403         markup = g_markup_printf_escaped (
1404                 "<span color=\"red\" weight=\"bold\">%s</span>",
1405                 name);
1406
1407         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1408         gtk_label_set_markup (GTK_LABEL (widget), markup);
1409
1410         widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1411         gtk_label_set_markup (GTK_LABEL (widget), markup);
1412
1413         g_free (name);
1414         g_free (markup);
1415 }
1416
1417 static gboolean
1418 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1419 {
1420         EmpathyChatWindowPriv *priv;
1421         gboolean              has_focus;
1422
1423         g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1424
1425         priv = GET_PRIV (window);
1426
1427         g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1428
1429         return has_focus;
1430 }
1431
1432 static void
1433 chat_window_new_message_cb (EmpathyChat       *chat,
1434                             EmpathyMessage    *message,
1435                             gboolean pending,
1436                             EmpathyChatWindow *window)
1437 {
1438         EmpathyChatWindowPriv *priv;
1439         gboolean              has_focus;
1440         gboolean              needs_urgency;
1441         EmpathyContact        *sender;
1442
1443         priv = GET_PRIV (window);
1444
1445         has_focus = empathy_chat_window_has_focus (window);
1446
1447         /* - if we're the sender, we play the sound if it's specified in the
1448          *   preferences and we're not away.
1449          * - if we receive a message, we play the sound if it's specified in the
1450          *   preferences and the window does not have focus on the chat receiving
1451          *   the message.
1452          */
1453
1454         sender = empathy_message_get_sender (message);
1455
1456         if (empathy_contact_is_user (sender)) {
1457                 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1458                                     EMPATHY_SOUND_MESSAGE_OUTGOING);
1459         }
1460
1461         if (has_focus && priv->current_chat == chat) {
1462                 /* window and tab are focused so consider the message to be read */
1463
1464                 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1465                 empathy_chat_messages_read (chat);
1466                 return;
1467         }
1468
1469         /* Update the chat tab if this is the first unread message */
1470         if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1471                 chat_window_update_chat_tab (chat);
1472         }
1473
1474         /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1475          * If empathy_chat_get_remote_contact () returns NULL, that means it's
1476          * an unamed MUC (msn-like).
1477          * In case of a MUC, we set urgency if either:
1478          *   a) the chatroom's always_urgent property is TRUE
1479          *   b) the message contains our alias
1480          */
1481         if (empathy_chat_is_room (chat) ||
1482             empathy_chat_get_remote_contact (chat) == NULL) {
1483                 TpAccount             *account;
1484                 const gchar           *room;
1485                 EmpathyChatroom       *chatroom;
1486
1487                 account = empathy_chat_get_account (chat);
1488                 room = empathy_chat_get_id (chat);
1489
1490                 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1491                                                           account, room);
1492
1493                 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1494                         needs_urgency = TRUE;
1495                 } else {
1496                         needs_urgency = empathy_message_should_highlight (message);
1497                 }
1498         } else {
1499                 needs_urgency = TRUE;
1500         }
1501
1502         if (needs_urgency) {
1503                 chat_window_set_highlight_room_labels (chat);
1504
1505                 if (!has_focus) {
1506                         chat_window_set_urgency_hint (window, TRUE);
1507                 }
1508
1509                 /* Pending messages have already been displayed and notified in the
1510                 * approver, so we don't display a notification and play a sound for those */
1511                 if (!pending) {
1512                         empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1513                                     EMPATHY_SOUND_MESSAGE_INCOMING);
1514
1515                         chat_window_show_or_update_notification (window, message, chat);
1516                 }
1517         }
1518
1519         /* update the number of unread messages and the window icon */
1520         chat_window_title_update (priv);
1521         chat_window_icon_update (priv, TRUE);
1522 }
1523
1524 static void
1525 chat_window_command_part (EmpathyChat *chat,
1526                            GStrv        strv)
1527 {
1528         EmpathyChat *chat_to_be_parted;
1529         EmpathyTpChat *tp_chat = NULL;
1530
1531         if (strv[1] == NULL) {
1532                 /* No chatroom ID specified  */
1533                 tp_chat = empathy_chat_get_tp_chat (chat);
1534                 if (tp_chat)
1535                         empathy_tp_chat_leave (tp_chat, "");
1536                 return;
1537         }
1538         chat_to_be_parted = empathy_chat_window_find_chat (
1539                 empathy_chat_get_account (chat), strv[1], FALSE);
1540
1541         if (chat_to_be_parted != NULL) {
1542                 /* Found a chatroom matching the specified ID */
1543                 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1544                 if (tp_chat)
1545                         empathy_tp_chat_leave (tp_chat, strv[2]);
1546         } else {
1547                 gchar *message;
1548
1549                 /* Going by the syntax of PART command:
1550                  *
1551                  * /PART [<chatroom-ID>] [<reason>]
1552                  *
1553                  * Chatroom-ID is not a must to specify a reason.
1554                  * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1555                  * MUC then the current chatroom should be parted and srtv[1] should
1556                  * be treated as part of the optional part-message. */
1557                 message = g_strconcat (strv[1], " ", strv[2], NULL);
1558                 tp_chat = empathy_chat_get_tp_chat (chat);
1559                 if (tp_chat)
1560                         empathy_tp_chat_leave (tp_chat, message);
1561
1562                 g_free (message);
1563         }
1564 }
1565
1566 static GtkNotebook *
1567 notebook_create_window_cb (GtkNotebook *source,
1568                          GtkWidget   *page,
1569                          gint         x,
1570                          gint         y,
1571                          gpointer     user_data)
1572 {
1573         EmpathyChatWindowPriv *priv;
1574         EmpathyChatWindow     *window, *new_window;
1575         EmpathyChat           *chat;
1576
1577         chat = EMPATHY_CHAT (page);
1578         window = chat_window_find_chat (chat);
1579
1580         new_window = empathy_chat_window_new ();
1581         priv = GET_PRIV (new_window);
1582
1583         DEBUG ("Detach hook called");
1584
1585         empathy_chat_window_move_chat (window, new_window, chat);
1586
1587         gtk_widget_show (priv->dialog);
1588         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1589
1590         return NULL;
1591 }
1592
1593 static void
1594 chat_window_page_switched_cb (GtkNotebook      *notebook,
1595                               GtkWidget         *child,
1596                               gint              page_num,
1597                               EmpathyChatWindow *window)
1598 {
1599         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1600         EmpathyChat           *chat = EMPATHY_CHAT (child);
1601
1602         DEBUG ("Page switched");
1603
1604         if (priv->page_added) {
1605                 priv->page_added = FALSE;
1606                 empathy_chat_scroll_down (chat);
1607         }
1608         else if (priv->current_chat == chat) {
1609                 return;
1610         }
1611
1612         priv->current_chat = chat;
1613         empathy_chat_messages_read (chat);
1614
1615         chat_window_update_chat_tab (chat);
1616 }
1617
1618 static void
1619 chat_window_page_added_cb (GtkNotebook      *notebook,
1620                            GtkWidget        *child,
1621                            guint             page_num,
1622                            EmpathyChatWindow *window)
1623 {
1624         EmpathyChatWindowPriv *priv;
1625         EmpathyChat           *chat;
1626
1627         priv = GET_PRIV (window);
1628
1629         /* If we just received DND to the same window, we don't want
1630          * to do anything here like removing the tab and then readding
1631          * it, so we return here and in "page-added".
1632          */
1633         if (priv->dnd_same_window) {
1634                 DEBUG ("Page added (back to the same window)");
1635                 priv->dnd_same_window = FALSE;
1636                 return;
1637         }
1638
1639         DEBUG ("Page added");
1640
1641         /* Get chat object */
1642         chat = EMPATHY_CHAT (child);
1643
1644         /* Connect chat signals for this window */
1645         g_signal_connect (chat, "composing",
1646                           G_CALLBACK (chat_window_composing_cb),
1647                           window);
1648         g_signal_connect (chat, "new-message",
1649                           G_CALLBACK (chat_window_new_message_cb),
1650                           window);
1651         g_signal_connect (chat, "part-command-entered",
1652                           G_CALLBACK (chat_window_command_part),
1653                           NULL);
1654         g_signal_connect (chat, "notify::tp-chat",
1655                           G_CALLBACK (chat_window_update_chat_tab),
1656                           window);
1657
1658         /* Set flag so we know to perform some special operations on
1659          * switch page due to the new page being added.
1660          */
1661         priv->page_added = TRUE;
1662
1663         /* Get list of chats up to date */
1664         priv->chats = g_list_append (priv->chats, chat);
1665
1666         chat_window_update_chat_tab (chat);
1667 }
1668
1669 static void
1670 chat_window_page_removed_cb (GtkNotebook      *notebook,
1671                              GtkWidget        *child,
1672                              guint             page_num,
1673                              EmpathyChatWindow *window)
1674 {
1675         EmpathyChatWindowPriv *priv;
1676         EmpathyChat           *chat;
1677
1678         priv = GET_PRIV (window);
1679
1680         /* If we just received DND to the same window, we don't want
1681          * to do anything here like removing the tab and then readding
1682          * it, so we return here and in "page-added".
1683          */
1684         if (priv->dnd_same_window) {
1685                 DEBUG ("Page removed (and will be readded to same window)");
1686                 return;
1687         }
1688
1689         DEBUG ("Page removed");
1690
1691         /* Get chat object */
1692         chat = EMPATHY_CHAT (child);
1693
1694         /* Disconnect all signal handlers for this chat and this window */
1695         g_signal_handlers_disconnect_by_func (chat,
1696                                               G_CALLBACK (chat_window_composing_cb),
1697                                               window);
1698         g_signal_handlers_disconnect_by_func (chat,
1699                                               G_CALLBACK (chat_window_new_message_cb),
1700                                               window);
1701         g_signal_handlers_disconnect_by_func (chat,
1702                                               G_CALLBACK (chat_window_update_chat_tab),
1703                                               window);
1704
1705         /* Keep list of chats up to date */
1706         priv->chats = g_list_remove (priv->chats, chat);
1707         empathy_chat_messages_read (chat);
1708
1709         if (priv->chats == NULL) {
1710                 g_object_unref (window);
1711         } else {
1712                 chat_window_update (window, TRUE);
1713         }
1714 }
1715
1716 static gboolean
1717 chat_window_focus_in_event_cb (GtkWidget        *widget,
1718                                GdkEvent         *event,
1719                                EmpathyChatWindow *window)
1720 {
1721         EmpathyChatWindowPriv *priv;
1722
1723         priv = GET_PRIV (window);
1724
1725         empathy_chat_messages_read (priv->current_chat);
1726
1727         chat_window_set_urgency_hint (window, FALSE);
1728
1729         /* Update the title, since we now mark all unread messages as read. */
1730         chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1731
1732         return FALSE;
1733 }
1734
1735 static gboolean
1736 chat_window_drag_drop (GtkWidget        *widget,
1737                          GdkDragContext   *context,
1738                          int               x,
1739                          int               y,
1740                          guint             time_,
1741                          EmpathyChatWindow *window)
1742 {
1743         GdkAtom target;
1744         EmpathyChatWindowPriv *priv;
1745
1746         priv = GET_PRIV (window);
1747
1748         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1749         if (target == GDK_NONE)
1750                 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1751
1752         if (target != GDK_NONE) {
1753                 gtk_drag_get_data (widget, context, target, time_);
1754                 return TRUE;
1755         }
1756
1757         return FALSE;
1758 }
1759
1760 static gboolean
1761 chat_window_drag_motion (GtkWidget        *widget,
1762                          GdkDragContext   *context,
1763                          int               x,
1764                          int               y,
1765                          guint             time_,
1766                          EmpathyChatWindow *window)
1767 {
1768         GdkAtom target;
1769         EmpathyChatWindowPriv *priv;
1770
1771         priv = GET_PRIV (window);
1772
1773         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1774         if (target != GDK_NONE) {
1775                 /* This is a file drag.  Ensure the contact is online and set the
1776                    drag type to COPY.  Note that it's possible that the tab will
1777                    be switched by GTK+ after a timeout from drag_motion without
1778                    getting another drag_motion to disable the drop.  You have
1779                    to hold your mouse really still.
1780                  */
1781                 EmpathyContact *contact;
1782
1783                 priv = GET_PRIV (window);
1784                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1785                 /* contact is NULL for multi-user chats.  We don't do
1786                  * file transfers to MUCs.  We also don't send files
1787                  * to offline contacts or contacts that don't support
1788                  * file transfer.
1789                  */
1790                 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1791                         gdk_drag_status (context, 0, time_);
1792                         return FALSE;
1793                 }
1794                 if (!(empathy_contact_get_capabilities (contact)
1795                            & EMPATHY_CAPABILITIES_FT)) {
1796                         gdk_drag_status (context, 0, time_);
1797                         return FALSE;
1798                 }
1799                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1800                 return TRUE;
1801         }
1802
1803         target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1804         if (target != GDK_NONE) {
1805                 /* This is a drag of a contact from a contact list.  Set to COPY.
1806                    FIXME: If this drag is to a MUC window, it invites the user.
1807                    Otherwise, it opens a chat.  Should we use a different drag
1808                    type for invites?  Should we allow ASK?
1809                  */
1810                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1811                 return TRUE;
1812         }
1813
1814         return FALSE;
1815 }
1816
1817 static void
1818 chat_window_drag_data_received (GtkWidget        *widget,
1819                                 GdkDragContext   *context,
1820                                 int               x,
1821                                 int               y,
1822                                 GtkSelectionData *selection,
1823                                 guint             info,
1824                                 guint             time_,
1825                                 EmpathyChatWindow *window)
1826 {
1827         if (info == DND_DRAG_TYPE_CONTACT_ID) {
1828                 EmpathyChat           *chat = NULL;
1829                 EmpathyChatWindow     *old_window;
1830                 TpAccount             *account = NULL;
1831                 EmpathyClientFactory  *factory;
1832                 const gchar           *id;
1833                 gchar                **strv;
1834                 const gchar           *account_id;
1835                 const gchar           *contact_id;
1836
1837                 id = (const gchar*) gtk_selection_data_get_data (selection);
1838
1839                 factory = empathy_client_factory_dup ();
1840
1841                 DEBUG ("DND contact from roster with id:'%s'", id);
1842
1843                 strv = g_strsplit (id, ":", 2);
1844                 if (g_strv_length (strv) == 2) {
1845                         account_id = strv[0];
1846                         contact_id = strv[1];
1847                         account =
1848                                 tp_simple_client_factory_ensure_account (
1849                                 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
1850                                  NULL, NULL);
1851
1852                         g_object_unref (factory);
1853                         if (account != NULL)
1854                                 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
1855                 }
1856
1857                 if (account == NULL) {
1858                         g_strfreev (strv);
1859                         gtk_drag_finish (context, FALSE, FALSE, time_);
1860                         return;
1861                 }
1862
1863                 if (!chat) {
1864                         empathy_chat_with_contact_id (
1865                                 account, contact_id, empathy_get_current_action_time ());
1866
1867                         g_strfreev (strv);
1868                         return;
1869                 }
1870                 g_strfreev (strv);
1871
1872                 old_window = chat_window_find_chat (chat);
1873                 if (old_window) {
1874                         if (old_window == window) {
1875                                 gtk_drag_finish (context, TRUE, FALSE, time_);
1876                                 return;
1877                         }
1878
1879                         empathy_chat_window_move_chat (old_window, window, chat);
1880                 } else {
1881                         empathy_chat_window_add_chat (window, chat);
1882                 }
1883
1884                 /* Added to take care of any outstanding chat events */
1885                 empathy_chat_window_present_chat (chat,
1886                         TP_USER_ACTION_TIME_NOT_USER_ACTION);
1887
1888                 /* We should return TRUE to remove the data when doing
1889                  * GDK_ACTION_MOVE, but we don't here otherwise it has
1890                  * weird consequences, and we handle that internally
1891                  * anyway with add_chat () and remove_chat ().
1892                  */
1893                 gtk_drag_finish (context, TRUE, FALSE, time_);
1894         }
1895         else if (info == DND_DRAG_TYPE_URI_LIST) {
1896                 EmpathyChatWindowPriv *priv;
1897                 EmpathyContact *contact;
1898                 const gchar *data;
1899
1900                 priv = GET_PRIV (window);
1901                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1902
1903                 /* contact is NULL when current_chat is a multi-user chat.
1904                  * We don't do file transfers to MUCs, so just cancel the drag.
1905                  */
1906                 if (contact == NULL) {
1907                         gtk_drag_finish (context, TRUE, FALSE, time_);
1908                         return;
1909                 }
1910
1911                 data = (const gchar *) gtk_selection_data_get_data (selection);
1912                 empathy_send_file_from_uri_list (contact, data);
1913
1914                 gtk_drag_finish (context, TRUE, FALSE, time_);
1915         }
1916         else if (info == DND_DRAG_TYPE_TAB) {
1917                 EmpathyChat        **chat;
1918                 EmpathyChatWindow   *old_window = NULL;
1919
1920                 DEBUG ("DND tab");
1921
1922                 chat = (void *) gtk_selection_data_get_data (selection);
1923                 old_window = chat_window_find_chat (*chat);
1924
1925                 if (old_window) {
1926                         EmpathyChatWindowPriv *priv;
1927
1928                         priv = GET_PRIV (window);
1929                         priv->dnd_same_window = (old_window == window);
1930                         DEBUG ("DND tab (within same window: %s)",
1931                                 priv->dnd_same_window ? "Yes" : "No");
1932                 }
1933         } else {
1934                 DEBUG ("DND from unknown source");
1935                 gtk_drag_finish (context, FALSE, FALSE, time_);
1936         }
1937 }
1938
1939 static void
1940 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1941                                            guint num_chats_in_manager,
1942                                            EmpathyChatWindow *window)
1943 {
1944         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1945
1946         gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1947                                   num_chats_in_manager > 0);
1948 }
1949
1950 static void
1951 chat_window_finalize (GObject *object)
1952 {
1953         EmpathyChatWindow     *window;
1954         EmpathyChatWindowPriv *priv;
1955
1956         window = EMPATHY_CHAT_WINDOW (object);
1957         priv = GET_PRIV (window);
1958
1959         DEBUG ("Finalized: %p", object);
1960
1961         g_object_unref (priv->ui_manager);
1962         g_object_unref (priv->chatroom_manager);
1963         g_object_unref (priv->notify_mgr);
1964         g_object_unref (priv->gsettings_chat);
1965         g_object_unref (priv->gsettings_notif);
1966         g_object_unref (priv->gsettings_ui);
1967         g_object_unref (priv->sound_mgr);
1968
1969         if (priv->notification != NULL) {
1970                 notify_notification_close (priv->notification, NULL);
1971                 priv->notification = NULL;
1972         }
1973
1974         if (priv->contact_targets) {
1975                 gtk_target_list_unref (priv->contact_targets);
1976         }
1977         if (priv->file_targets) {
1978                 gtk_target_list_unref (priv->file_targets);
1979         }
1980
1981         if (priv->chat_manager) {
1982                 g_signal_handler_disconnect (priv->chat_manager,
1983                                              priv->chat_manager_chats_changed_id);
1984                 g_object_unref (priv->chat_manager);
1985                 priv->chat_manager = NULL;
1986         }
1987
1988         chat_windows = g_list_remove (chat_windows, window);
1989         gtk_widget_destroy (priv->dialog);
1990
1991         G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1992 }
1993
1994 static void
1995 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1996 {
1997         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1998
1999         object_class->finalize = chat_window_finalize;
2000
2001         g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2002 }
2003
2004 static void
2005 empathy_chat_window_init (EmpathyChatWindow *window)
2006 {
2007         GtkBuilder            *gui;
2008         GtkAccelGroup         *accel_group;
2009         GClosure              *closure;
2010         GtkWidget             *menu;
2011         GtkWidget             *submenu;
2012         guint                  i;
2013         GtkWidget             *chat_vbox;
2014         gchar                 *filename;
2015         EmpathySmileyManager  *smiley_manager;
2016         EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2017                 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2018
2019         window->priv = priv;
2020         filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2021         gui = empathy_builder_get_file (filename,
2022                                        "chat_window", &priv->dialog,
2023                                        "chat_vbox", &chat_vbox,
2024                                        "ui_manager", &priv->ui_manager,
2025                                        "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2026                                        "menu_conv_favorite", &priv->menu_conv_favorite,
2027                                        "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2028                                        "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2029                                        "menu_edit_cut", &priv->menu_edit_cut,
2030                                        "menu_edit_copy", &priv->menu_edit_copy,
2031                                        "menu_edit_paste", &priv->menu_edit_paste,
2032                                        "menu_edit_find", &priv->menu_edit_find,
2033                                        "menu_tabs_next", &priv->menu_tabs_next,
2034                                        "menu_tabs_prev", &priv->menu_tabs_prev,
2035                                        "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2036                                        "menu_tabs_left", &priv->menu_tabs_left,
2037                                        "menu_tabs_right", &priv->menu_tabs_right,
2038                                        "menu_tabs_detach", &priv->menu_tabs_detach,
2039                                        NULL);
2040         g_free (filename);
2041
2042         empathy_builder_connect (gui, window,
2043                               "menu_conv", "activate", chat_window_conv_activate_cb,
2044                               "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2045                               "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2046                               "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2047                               "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2048                               "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2049                               "menu_conv_close", "activate", chat_window_close_activate_cb,
2050                               "menu_edit", "activate", chat_window_edit_activate_cb,
2051                               "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2052                               "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2053                               "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2054                               "menu_edit_find", "activate", chat_window_find_activate_cb,
2055                               "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2056                               "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2057                               "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2058                               "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2059                               "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2060                               "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2061                               "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2062                               "menu_help_about", "activate", chat_window_help_about_activate_cb,
2063                               NULL);
2064
2065         g_object_ref (priv->ui_manager);
2066         g_object_unref (gui);
2067
2068         priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2069         priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2070         priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2071         priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2072
2073         priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2074
2075         priv->notebook = gtk_notebook_new ();
2076
2077         g_signal_connect (priv->notebook, "create-window",
2078                 G_CALLBACK (notebook_create_window_cb), window);
2079
2080         gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2081                 "EmpathyChatWindow");
2082         gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2083         gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2084         gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2085         gtk_widget_show (priv->notebook);
2086
2087         /* Set up accels */
2088         accel_group = gtk_accel_group_new ();
2089         gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2090
2091         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2092                 closure =  g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2093                                            window,
2094                                            NULL);
2095                 gtk_accel_group_connect (accel_group,
2096                                          tab_accel_keys[i],
2097                                          GDK_MOD1_MASK,
2098                                          0,
2099                                          closure);
2100         }
2101
2102         g_object_unref (accel_group);
2103
2104         /* Set up drag target lists */
2105         priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2106                                                      G_N_ELEMENTS (drag_types_dest_contact));
2107         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2108                                                   G_N_ELEMENTS (drag_types_dest_file));
2109
2110         /* Set up smiley menu */
2111         smiley_manager = empathy_smiley_manager_dup_singleton ();
2112         submenu = empathy_smiley_menu_new (smiley_manager,
2113                                            chat_window_insert_smiley_activate_cb,
2114                                            window);
2115         menu = gtk_ui_manager_get_widget (priv->ui_manager,
2116                 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2117         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2118         g_object_unref (smiley_manager);
2119
2120         /* Set up signals we can't do with ui file since we may need to
2121          * block/unblock them at some later stage.
2122          */
2123
2124         g_signal_connect (priv->dialog,
2125                           "delete_event",
2126                           G_CALLBACK (chat_window_delete_event_cb),
2127                           window);
2128         g_signal_connect (priv->dialog,
2129                           "focus_in_event",
2130                           G_CALLBACK (chat_window_focus_in_event_cb),
2131                           window);
2132         g_signal_connect_after (priv->notebook,
2133                                 "switch_page",
2134                                 G_CALLBACK (chat_window_page_switched_cb),
2135                                 window);
2136         g_signal_connect (priv->notebook,
2137                           "page_added",
2138                           G_CALLBACK (chat_window_page_added_cb),
2139                           window);
2140         g_signal_connect (priv->notebook,
2141                           "page_removed",
2142                           G_CALLBACK (chat_window_page_removed_cb),
2143                           window);
2144
2145         /* Set up drag and drop */
2146         gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2147                            GTK_DEST_DEFAULT_HIGHLIGHT,
2148                            drag_types_dest,
2149                            G_N_ELEMENTS (drag_types_dest),
2150                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2151
2152         /* connect_after to allow GtkNotebook's built-in tab switching */
2153         g_signal_connect_after (priv->notebook,
2154                                 "drag-motion",
2155                                 G_CALLBACK (chat_window_drag_motion),
2156                                 window);
2157         g_signal_connect (priv->notebook,
2158                           "drag-data-received",
2159                           G_CALLBACK (chat_window_drag_data_received),
2160                           window);
2161         g_signal_connect (priv->notebook,
2162                           "drag-drop",
2163                           G_CALLBACK (chat_window_drag_drop),
2164                           window);
2165
2166         chat_windows = g_list_prepend (chat_windows, window);
2167
2168         /* Set up private details */
2169         priv->chats = NULL;
2170         priv->current_chat = NULL;
2171         priv->notification = NULL;
2172
2173         priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2174
2175         priv->chat_manager = empathy_chat_manager_dup_singleton ();
2176         priv->chat_manager_chats_changed_id =
2177                 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2178                                   G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2179                                   window);
2180
2181         chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2182                                                    empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2183                                                    window);
2184 }
2185
2186 /* Returns the window to open a new tab in if there is a suitable window,
2187  * otherwise, returns NULL indicating that a new window should be added.
2188  */
2189 static EmpathyChatWindow *
2190 empathy_chat_window_get_default (gboolean room)
2191 {
2192         GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2193         GList    *l;
2194         gboolean  separate_windows = TRUE;
2195
2196         separate_windows = g_settings_get_boolean (gsettings,
2197                         EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2198
2199         g_object_unref (gsettings);
2200
2201         if (separate_windows) {
2202                 /* Always create a new window */
2203                 return NULL;
2204         }
2205
2206         for (l = chat_windows; l; l = l->next) {
2207                 EmpathyChatWindow *chat_window;
2208                 guint nb_rooms, nb_private;
2209
2210                 chat_window = l->data;
2211
2212                 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2213
2214                 /* Skip the window if there aren't any rooms in it */
2215                 if (room && nb_rooms == 0)
2216                         continue;
2217
2218                 /* Skip the window if there aren't any 1-1 chats in it */
2219                 if (!room && nb_private == 0)
2220                         continue;
2221
2222                 return chat_window;
2223         }
2224
2225         return NULL;
2226 }
2227
2228 static void
2229 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2230                               EmpathyChat       *chat)
2231 {
2232         EmpathyChatWindowPriv *priv;
2233         GtkWidget             *label;
2234         GtkWidget             *popup_label;
2235         GtkWidget             *child;
2236         GValue                value = { 0, };
2237
2238         g_return_if_fail (window != NULL);
2239         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2240
2241         priv = GET_PRIV (window);
2242
2243         /* Reference the chat object */
2244         g_object_ref (chat);
2245
2246         /* If this window has just been created, position it */
2247         if (priv->chats == NULL) {
2248                 const gchar *name = "chat-window";
2249                 gboolean     separate_windows;
2250
2251                 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2252                                 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2253
2254                 if (empathy_chat_is_room (chat))
2255                         name = "room-window";
2256
2257                 if (separate_windows) {
2258                         gint x, y;
2259
2260                         /* Save current position of the window */
2261                         gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2262
2263                         /* First bind to the 'generic' name. So new window for which we didn't
2264                         * save a geometry yet will have the geometry of the last saved
2265                         * window (bgo #601191). */
2266                         empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2267
2268                         /* Restore previous position of the window so the newly created window
2269                         * won't be in the same position as the latest saved window and so
2270                         * completely hide it. */
2271                         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2272
2273                         /* Then bind it to the name of the contact/room so we'll save the
2274                         * geometry specific to this window */
2275                         name = empathy_chat_get_id (chat);
2276                 }
2277
2278                 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2279         }
2280
2281         child = GTK_WIDGET (chat);
2282         label = chat_window_create_label (window, chat, TRUE);
2283         popup_label = chat_window_create_label (window, chat, FALSE);
2284         gtk_widget_show (child);
2285
2286         g_signal_connect (chat, "notify::name",
2287                           G_CALLBACK (chat_window_chat_notify_cb),
2288                           NULL);
2289         g_signal_connect (chat, "notify::subject",
2290                           G_CALLBACK (chat_window_chat_notify_cb),
2291                           NULL);
2292         g_signal_connect (chat, "notify::remote-contact",
2293                           G_CALLBACK (chat_window_chat_notify_cb),
2294                           NULL);
2295         g_signal_connect (chat, "notify::sms-channel",
2296                           G_CALLBACK (chat_window_chat_notify_cb),
2297                           NULL);
2298         g_signal_connect (chat, "notify::n-messages-sending",
2299                           G_CALLBACK (chat_window_chat_notify_cb),
2300                           NULL);
2301         g_signal_connect (chat, "notify::nb-unread-messages",
2302                           G_CALLBACK (chat_window_chat_notify_cb),
2303                           NULL);
2304         chat_window_chat_notify_cb (chat);
2305
2306         gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2307         gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2308         gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2309         g_value_init (&value, G_TYPE_BOOLEAN);
2310         g_value_set_boolean (&value, TRUE);
2311         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2312                                           child, "tab-expand" , &value);
2313         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2314                                           child,  "tab-fill" , &value);
2315         g_value_unset (&value);
2316
2317         DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2318 }
2319
2320 static void
2321 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2322                                  EmpathyChat       *chat)
2323 {
2324         EmpathyChatWindowPriv *priv;
2325         gint                   position;
2326         EmpathyContact        *remote_contact;
2327         EmpathyChatManager    *chat_manager;
2328
2329         g_return_if_fail (window != NULL);
2330         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2331
2332         priv = GET_PRIV (window);
2333
2334         g_signal_handlers_disconnect_by_func (chat,
2335                                               chat_window_chat_notify_cb,
2336                                               NULL);
2337         remote_contact = g_object_get_data (G_OBJECT (chat),
2338                                             "chat-window-remote-contact");
2339         if (remote_contact) {
2340                 g_signal_handlers_disconnect_by_func (remote_contact,
2341                                                       chat_window_update_chat_tab,
2342                                                       chat);
2343         }
2344
2345         chat_manager = empathy_chat_manager_dup_singleton ();
2346         empathy_chat_manager_closed_chat (chat_manager, chat);
2347         g_object_unref (chat_manager);
2348
2349         position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2350                                           GTK_WIDGET (chat));
2351         gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2352
2353         DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2354
2355         g_object_unref (chat);
2356 }
2357
2358 static void
2359 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2360                                EmpathyChatWindow *new_window,
2361                                EmpathyChat       *chat)
2362 {
2363         GtkWidget *widget;
2364
2365         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2366         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2367         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2368
2369         widget = GTK_WIDGET (chat);
2370
2371         DEBUG ("Chat moving with widget:%p (%d references)", widget,
2372                 G_OBJECT (widget)->ref_count);
2373
2374         /* We reference here to make sure we don't loose the widget
2375          * and the EmpathyChat object during the move.
2376          */
2377         g_object_ref (chat);
2378         g_object_ref (widget);
2379
2380         empathy_chat_window_remove_chat (old_window, chat);
2381         empathy_chat_window_add_chat (new_window, chat);
2382
2383         g_object_unref (widget);
2384         g_object_unref (chat);
2385 }
2386
2387 static void
2388 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2389                                     EmpathyChat       *chat)
2390 {
2391         EmpathyChatWindowPriv *priv;
2392         gint                  page_num;
2393
2394         g_return_if_fail (window != NULL);
2395         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2396
2397         priv = GET_PRIV (window);
2398
2399         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2400                                           GTK_WIDGET (chat));
2401         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2402                                        page_num);
2403 }
2404
2405 EmpathyChat *
2406 empathy_chat_window_find_chat (TpAccount   *account,
2407                                const gchar *id,
2408                                gboolean     sms_channel)
2409 {
2410         GList *l;
2411
2412         g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2413
2414         for (l = chat_windows; l; l = l->next) {
2415                 EmpathyChatWindowPriv *priv;
2416                 EmpathyChatWindow     *window;
2417                 GList                *ll;
2418
2419                 window = l->data;
2420                 priv = GET_PRIV (window);
2421
2422                 for (ll = priv->chats; ll; ll = ll->next) {
2423                         EmpathyChat *chat;
2424
2425                         chat = ll->data;
2426
2427                         if (account == empathy_chat_get_account (chat) &&
2428                             !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2429                             sms_channel == empathy_chat_is_sms_channel (chat)) {
2430                                 return chat;
2431                         }
2432                 }
2433         }
2434
2435         return NULL;
2436 }
2437
2438 void
2439 empathy_chat_window_present_chat (EmpathyChat *chat,
2440                                   gint64 timestamp)
2441 {
2442         EmpathyChatWindow     *window;
2443         EmpathyChatWindowPriv *priv;
2444         guint32 x_timestamp;
2445
2446         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2447
2448         window = chat_window_find_chat (chat);
2449
2450         /* If the chat has no window, create one */
2451         if (window == NULL) {
2452                 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2453                 if (!window) {
2454                         window = empathy_chat_window_new ();
2455
2456                         /* we want to display the newly created window even if we don't present
2457                         * it */
2458                         priv = GET_PRIV (window);
2459                         gtk_widget_show (priv->dialog);
2460                 }
2461
2462                 empathy_chat_window_add_chat (window, chat);
2463         }
2464
2465         /* Don't force the window to show itself when it wasn't
2466          * an action by the user
2467          */
2468         if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2469                 return;
2470
2471         priv = GET_PRIV (window);
2472
2473         if (x_timestamp != GDK_CURRENT_TIME) {
2474                 /* Don't present or switch tab if the action was earlier than the
2475                  * last actions X time, accounting for overflow and the first ever
2476                 * presentation */
2477
2478                 if (priv->x_user_action_time != 0
2479                         && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2480                         return;
2481
2482                 priv->x_user_action_time = x_timestamp;
2483         }
2484
2485         empathy_chat_window_switch_to_chat (window, chat);
2486         empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2487           x_timestamp);
2488
2489         gtk_widget_grab_focus (chat->input_text_view);
2490 }
2491
2492 static void
2493 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2494                                guint *nb_rooms,
2495                                guint *nb_private)
2496 {
2497         EmpathyChatWindowPriv *priv = GET_PRIV (self);
2498         GList *l;
2499         guint _nb_rooms = 0, _nb_private = 0;
2500
2501         for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2502                 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2503                         _nb_rooms++;
2504                 else
2505                         _nb_private++;
2506         }
2507
2508         if (nb_rooms != NULL)
2509                 *nb_rooms = _nb_rooms;
2510         if (nb_private != NULL)
2511                 *nb_private = _nb_private;
2512 }