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