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