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