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