1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2003-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
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.
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.
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
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>
34 #include <gdk/gdkkeysyms.h>
36 #include <glib/gi18n.h>
37 #include <libnotify/notification.h>
39 #include <telepathy-glib/telepathy-glib.h>
41 #include <libempathy/empathy-contact.h>
42 #include <libempathy/empathy-message.h>
43 #include <libempathy/empathy-chatroom-manager.h>
44 #include <libempathy/empathy-gsettings.h>
45 #include <libempathy/empathy-utils.h>
46 #include <libempathy/empathy-tp-contact-factory.h>
47 #include <libempathy/empathy-contact-list.h>
49 #include <libempathy-gtk/empathy-images.h>
50 #include <libempathy-gtk/empathy-contact-dialogs.h>
51 #include <libempathy-gtk/empathy-log-window.h>
52 #include <libempathy-gtk/empathy-geometry.h>
53 #include <libempathy-gtk/empathy-smiley-manager.h>
54 #include <libempathy-gtk/empathy-sound.h>
55 #include <libempathy-gtk/empathy-ui-utils.h>
56 #include <libempathy-gtk/empathy-notify-manager.h>
58 #include "empathy-chat-manager.h"
59 #include "empathy-chat-window.h"
60 #include "empathy-about-dialog.h"
61 #include "empathy-invite-participant-dialog.h"
63 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
64 #include <libempathy/empathy-debug.h>
66 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
68 #define X_EARLIER_OR_EQL(t1, t2) \
69 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
70 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
73 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
75 EmpathyChat *current_chat;
78 GList *chats_composing;
80 gboolean dnd_same_window;
81 EmpathyChatroomManager *chatroom_manager;
82 EmpathyNotifyManager *notify_mgr;
85 NotifyNotification *notification;
87 GtkTargetList *contact_targets;
88 GtkTargetList *file_targets;
90 EmpathyChatManager *chat_manager;
91 gulong chat_manager_chats_changed_id;
94 GtkUIManager *ui_manager;
95 GtkAction *menu_conv_insert_smiley;
96 GtkAction *menu_conv_favorite;
97 GtkAction *menu_conv_always_urgent;
98 GtkAction *menu_conv_toggle_contacts;
100 GtkAction *menu_edit_cut;
101 GtkAction *menu_edit_copy;
102 GtkAction *menu_edit_paste;
103 GtkAction *menu_edit_find;
105 GtkAction *menu_tabs_next;
106 GtkAction *menu_tabs_prev;
107 GtkAction *menu_tabs_undo_close_tab;
108 GtkAction *menu_tabs_left;
109 GtkAction *menu_tabs_right;
110 GtkAction *menu_tabs_detach;
112 /* Last user action time we acted upon to show a tab */
113 guint32 x_user_action_time;
114 } EmpathyChatWindowPriv;
116 static GList *chat_windows = NULL;
118 static const guint tab_accel_keys[] = {
119 GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
120 GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
124 DND_DRAG_TYPE_CONTACT_ID,
125 DND_DRAG_TYPE_URI_LIST,
129 static const GtkTargetEntry drag_types_dest[] = {
130 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
131 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
132 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
133 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
136 static const GtkTargetEntry drag_types_dest_contact[] = {
137 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
140 static const GtkTargetEntry drag_types_dest_file[] = {
141 /* must be first to be prioritized, in order to receive the
142 * note's file path from Tomboy instead of an URI */
143 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
144 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
147 static void chat_window_update (EmpathyChatWindow *window);
149 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
152 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
155 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
156 EmpathyChatWindow *new_window,
159 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
163 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
166 chat_window_accel_cb (GtkAccelGroup *accelgroup,
170 EmpathyChatWindow *window)
172 EmpathyChatWindowPriv *priv;
176 priv = GET_PRIV (window);
178 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
179 if (tab_accel_keys[i] == key) {
186 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
190 static EmpathyChatWindow *
191 chat_window_find_chat (EmpathyChat *chat)
193 EmpathyChatWindowPriv *priv;
196 for (l = chat_windows; l; l = l->next) {
197 priv = GET_PRIV (l->data);
198 ll = g_list_find (priv->chats, chat);
208 chat_window_close_clicked_cb (GtkAction *action,
211 EmpathyChatWindow *window;
213 window = chat_window_find_chat (chat);
214 empathy_chat_window_remove_chat (window, chat);
218 chat_tab_style_set_cb (GtkWidget *hbox,
219 GtkStyle *previous_style,
223 int char_width, h, w;
224 PangoContext *context;
225 PangoFontMetrics *metrics;
227 button = g_object_get_data (G_OBJECT (user_data),
228 "chat-window-tab-close-button");
229 context = gtk_widget_get_pango_context (hbox);
231 metrics = pango_context_get_metrics (context, gtk_widget_get_style (hbox)->font_desc,
232 pango_context_get_language (context));
233 char_width = pango_font_metrics_get_approximate_char_width (metrics);
234 pango_font_metrics_unref (metrics);
236 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
237 GTK_ICON_SIZE_MENU, &w, &h);
239 /* Request at least about 12 chars width plus at least space for the status
240 * image and the close button */
241 gtk_widget_set_size_request (hbox,
242 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
244 gtk_widget_set_size_request (button, w, h);
248 chat_window_create_label (EmpathyChatWindow *window,
250 gboolean is_tab_label)
252 EmpathyChatWindowPriv *priv;
254 GtkWidget *name_label;
255 GtkWidget *status_image;
256 GtkWidget *close_button;
257 GtkWidget *close_image;
258 GtkWidget *event_box;
259 GtkWidget *event_box_hbox;
260 PangoAttrList *attr_list;
261 PangoAttribute *attr;
263 priv = GET_PRIV (window);
265 /* The spacing between the button and the label. */
266 hbox = gtk_hbox_new (FALSE, 0);
268 event_box = gtk_event_box_new ();
269 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
271 name_label = gtk_label_new (NULL);
273 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
275 attr_list = pango_attr_list_new ();
276 attr = pango_attr_scale_new (1/1.2);
277 attr->start_index = 0;
278 attr->end_index = -1;
279 pango_attr_list_insert (attr_list, attr);
280 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
281 pango_attr_list_unref (attr_list);
283 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
284 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
285 g_object_set_data (G_OBJECT (chat),
286 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
289 status_image = gtk_image_new ();
291 /* Spacing between the icon and label. */
292 event_box_hbox = gtk_hbox_new (FALSE, 0);
294 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
295 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
297 g_object_set_data (G_OBJECT (chat),
298 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
300 g_object_set_data (G_OBJECT (chat),
301 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
304 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
305 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
308 close_button = gtk_button_new ();
309 gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
310 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
312 /* We don't want focus/keynav for the button to avoid clutter, and
313 * Ctrl-W works anyway.
315 gtk_widget_set_can_focus (close_button, FALSE);
316 gtk_widget_set_can_default (close_button, FALSE);
318 /* Set the name to make the special rc style match. */
319 gtk_widget_set_name (close_button, "empathy-close-button");
321 close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
323 gtk_container_add (GTK_CONTAINER (close_button), close_image);
325 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
327 g_signal_connect (close_button,
329 G_CALLBACK (chat_window_close_clicked_cb),
332 /* React to theme changes and also setup the size correctly. */
333 g_signal_connect (hbox,
335 G_CALLBACK (chat_tab_style_set_cb),
339 gtk_widget_show_all (hbox);
345 _submenu_notify_visible_changed_cb (GObject *object,
349 g_signal_handlers_disconnect_by_func (object,
350 _submenu_notify_visible_changed_cb,
352 chat_window_update (EMPATHY_CHAT_WINDOW (userdata));
356 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
361 gboolean wrap_around;
362 gboolean is_connected;
365 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
366 first_page = (page_num == 0);
367 last_page = (page_num == (num_pages - 1));
368 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
370 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
372 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
374 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
376 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
377 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
378 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
379 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
383 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
384 EmpathyChatWindow *self)
386 EmpathyTpChat *tp_chat;
387 TpConnection *connection;
389 gboolean sensitive = FALSE;
391 g_return_if_fail (priv->current_chat != NULL);
393 action = gtk_ui_manager_get_action (priv->ui_manager,
394 "/chats_menubar/menu_conv/menu_conv_invite_participant");
395 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
397 if (tp_chat != NULL) {
398 connection = empathy_tp_chat_get_connection (tp_chat);
400 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
401 (tp_connection_get_status (connection, NULL) ==
402 TP_CONNECTION_STATUS_CONNECTED);
405 gtk_action_set_sensitive (action, sensitive);
409 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
410 EmpathyChatWindow *window)
412 GtkWidget *menu, *submenu, *orig_submenu;
414 menu = gtk_ui_manager_get_widget (priv->ui_manager,
415 "/chats_menubar/menu_contact");
416 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
418 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
419 submenu = empathy_chat_get_contact_menu (priv->current_chat);
420 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
421 gtk_widget_show (menu);
423 tp_g_signal_connect_object (orig_submenu,
425 (GCallback)_submenu_notify_visible_changed_cb,
431 get_all_unread_messages (EmpathyChatWindowPriv *priv)
436 for (l = priv->chats_new_msg; l != NULL; l = g_list_next (l)) {
437 EmpathyChat *chat = l->data;
439 nb += empathy_chat_get_nb_unread_messages (chat);
446 get_window_title_name (EmpathyChatWindowPriv *priv)
448 const gchar *active_name;
450 guint current_unread_msgs;
452 nb_chats = g_list_length (priv->chats);
453 g_assert (nb_chats > 0);
455 active_name = empathy_chat_get_name (priv->current_chat);
457 current_unread_msgs = empathy_chat_get_nb_unread_messages (
462 if (current_unread_msgs == 0)
463 return g_strdup (active_name);
465 return g_strdup_printf (ngettext (
467 "%s (%d unread)", current_unread_msgs),
468 active_name, current_unread_msgs);
470 guint nb_others = nb_chats - 1;
471 guint all_unread_msgs;
473 all_unread_msgs = get_all_unread_messages (priv);
475 if (all_unread_msgs == 0) {
476 /* no unread message */
477 return g_strdup_printf (ngettext (
479 "%s (and %u others)", nb_others),
480 active_name, nb_others);
483 else if (all_unread_msgs == current_unread_msgs) {
484 /* unread messages are in the current tab */
485 return g_strdup_printf (ngettext (
487 "%s (%d unread)", current_unread_msgs),
488 active_name, current_unread_msgs);
491 else if (current_unread_msgs == 0) {
492 /* unread messages are in other tabs */
493 return g_strdup_printf (ngettext (
494 "%s (%d unread from others)",
495 "%s (%d unread from others)",
497 active_name, all_unread_msgs);
501 /* unread messages are in all the tabs */
502 return g_strdup_printf (ngettext (
503 "%s (%d unread from all)",
504 "%s (%d unread from all)",
506 active_name, all_unread_msgs);
512 chat_window_title_update (EmpathyChatWindowPriv *priv)
516 name = get_window_title_name (priv);
517 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
522 chat_window_icon_update (EmpathyChatWindowPriv *priv)
525 EmpathyContact *remote_contact;
526 gboolean avatar_in_icon;
529 n_chats = g_list_length (priv->chats);
531 /* Update window icon */
532 if (priv->chats_new_msg) {
533 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
534 EMPATHY_IMAGE_MESSAGE);
536 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
538 avatar_in_icon = g_settings_get_boolean (gsettings,
539 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
541 if (n_chats == 1 && avatar_in_icon) {
542 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
543 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
544 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
547 g_object_unref (icon);
550 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
553 g_object_unref (gsettings);
558 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
562 GtkWidget *chat_close_button;
565 if (num_pages == 1) {
566 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
567 chat_close_button = g_object_get_data (G_OBJECT (chat),
568 "chat-window-tab-close-button");
569 gtk_widget_hide (chat_close_button);
571 for (i=0; i<num_pages; i++) {
572 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
573 chat_close_button = g_object_get_data (G_OBJECT (chat),
574 "chat-window-tab-close-button");
575 gtk_widget_show (chat_close_button);
581 chat_window_update (EmpathyChatWindow *window)
583 EmpathyChatWindowPriv *priv = GET_PRIV (window);
586 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
588 /* Update Tab menu */
589 chat_window_menu_context_update (priv,
592 chat_window_conversation_menu_update (priv, window);
594 chat_window_contact_menu_update (priv,
597 chat_window_title_update (priv);
599 chat_window_icon_update (priv);
601 chat_window_close_button_update (priv,
606 append_markup_printf (GString *string,
613 va_start (args, format);
615 tmp = g_markup_vprintf_escaped (format, args);
616 g_string_append (string, tmp);
623 chat_window_update_chat_tab (EmpathyChat *chat)
625 EmpathyChatWindow *window;
626 EmpathyChatWindowPriv *priv;
627 EmpathyContact *remote_contact;
631 const gchar *subject;
632 const gchar *status = NULL;
636 const gchar *icon_name;
637 GtkWidget *tab_image;
638 GtkWidget *menu_image;
640 window = chat_window_find_chat (chat);
644 priv = GET_PRIV (window);
646 /* Get information */
647 name = empathy_chat_get_name (chat);
648 account = empathy_chat_get_account (chat);
649 subject = empathy_chat_get_subject (chat);
650 remote_contact = empathy_chat_get_remote_contact (chat);
652 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
653 name, tp_proxy_get_object_path (account), subject, remote_contact);
655 /* Update tab image */
656 if (empathy_chat_get_tp_chat (chat) == NULL) {
657 /* No TpChat, we are disconnected */
660 else if (g_list_find (priv->chats_new_msg, chat)) {
661 icon_name = EMPATHY_IMAGE_MESSAGE;
663 else if (g_list_find (priv->chats_composing, chat)) {
664 icon_name = EMPATHY_IMAGE_TYPING;
666 else if (remote_contact) {
667 icon_name = empathy_icon_name_for_contact (remote_contact);
669 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
672 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
673 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
674 if (icon_name != NULL) {
675 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
676 gtk_widget_show (tab_image);
677 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
678 gtk_widget_show (menu_image);
680 gtk_widget_hide (tab_image);
681 gtk_widget_hide (menu_image);
684 /* Update tab tooltip */
685 tooltip = g_string_new (NULL);
687 if (remote_contact) {
688 id = empathy_contact_get_id (remote_contact);
689 status = empathy_contact_get_presence_message (remote_contact);
694 append_markup_printf (tooltip,
695 "<b>%s</b><small> (%s)</small>",
697 tp_account_get_display_name (account));
699 if (!EMP_STR_EMPTY (status)) {
700 append_markup_printf (tooltip, "\n<i>%s</i>", status);
704 append_markup_printf (tooltip, "\n<b>%s</b> %s",
705 _("Topic:"), subject);
708 if (g_list_find (priv->chats_composing, chat)) {
709 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
712 markup = g_string_free (tooltip, FALSE);
713 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
714 gtk_widget_set_tooltip_markup (widget, markup);
715 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
716 gtk_widget_set_tooltip_markup (widget, markup);
719 /* Update tab and menu label */
720 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
721 gtk_label_set_text (GTK_LABEL (widget), name);
722 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
723 gtk_label_set_text (GTK_LABEL (widget), name);
725 /* Update the window if it's the current chat */
726 if (priv->current_chat == chat) {
727 chat_window_update (window);
732 chat_window_chat_notify_cb (EmpathyChat *chat)
734 EmpathyContact *old_remote_contact;
735 EmpathyContact *remote_contact = NULL;
737 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
738 remote_contact = empathy_chat_get_remote_contact (chat);
740 if (old_remote_contact != remote_contact) {
741 /* The remote-contact associated with the chat changed, we need
742 * to keep track of any change of that contact and update the
743 * window each time. */
744 if (remote_contact) {
745 g_signal_connect_swapped (remote_contact, "notify",
746 G_CALLBACK (chat_window_update_chat_tab),
749 if (old_remote_contact) {
750 g_signal_handlers_disconnect_by_func (old_remote_contact,
751 chat_window_update_chat_tab,
755 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
756 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
759 chat_window_update_chat_tab (chat);
763 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
764 EmpathySmiley *smiley,
767 EmpathyChatWindowPriv *priv = GET_PRIV (window);
769 GtkTextBuffer *buffer;
772 chat = priv->current_chat;
774 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
775 gtk_text_buffer_get_end_iter (buffer, &iter);
776 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
780 chat_window_conv_activate_cb (GtkAction *action,
781 EmpathyChatWindow *window)
783 EmpathyChatWindowPriv *priv = GET_PRIV (window);
786 EmpathyContact *remote_contact = NULL;
788 /* Favorite room menu */
789 is_room = empathy_chat_is_room (priv->current_chat);
793 gboolean found = FALSE;
794 EmpathyChatroom *chatroom;
796 room = empathy_chat_get_id (priv->current_chat);
797 account = empathy_chat_get_account (priv->current_chat);
798 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
800 if (chatroom != NULL)
801 found = empathy_chatroom_is_favorite (chatroom);
803 DEBUG ("This room %s favorite", found ? "is" : "is not");
804 gtk_toggle_action_set_active (
805 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
807 if (chatroom != NULL)
808 found = empathy_chatroom_is_always_urgent (chatroom);
810 gtk_toggle_action_set_active (
811 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
814 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
815 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
817 /* Show contacts menu */
818 g_object_get (priv->current_chat,
819 "remote-contact", &remote_contact,
820 "show-contacts", &active,
822 if (remote_contact == NULL) {
823 gtk_toggle_action_set_active (
824 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
827 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
828 (remote_contact == NULL));
829 if (remote_contact != NULL) {
830 g_object_unref (remote_contact);
835 chat_window_clear_activate_cb (GtkAction *action,
836 EmpathyChatWindow *window)
838 EmpathyChatWindowPriv *priv = GET_PRIV (window);
840 empathy_chat_clear (priv->current_chat);
844 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
845 EmpathyChatWindow *window)
847 EmpathyChatWindowPriv *priv = GET_PRIV (window);
851 EmpathyChatroom *chatroom;
853 active = gtk_toggle_action_get_active (toggle_action);
854 account = empathy_chat_get_account (priv->current_chat);
855 room = empathy_chat_get_id (priv->current_chat);
857 chatroom = empathy_chatroom_manager_ensure_chatroom (
858 priv->chatroom_manager,
861 empathy_chat_get_name (priv->current_chat));
863 empathy_chatroom_set_favorite (chatroom, active);
864 g_object_unref (chatroom);
868 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
869 EmpathyChatWindow *window)
871 EmpathyChatWindowPriv *priv = GET_PRIV (window);
875 EmpathyChatroom *chatroom;
877 active = gtk_toggle_action_get_active (toggle_action);
878 account = empathy_chat_get_account (priv->current_chat);
879 room = empathy_chat_get_id (priv->current_chat);
881 chatroom = empathy_chatroom_manager_ensure_chatroom (
882 priv->chatroom_manager,
885 empathy_chat_get_name (priv->current_chat));
887 empathy_chatroom_set_always_urgent (chatroom, active);
888 g_object_unref (chatroom);
892 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
893 EmpathyChatWindow *window)
895 EmpathyChatWindowPriv *priv = GET_PRIV (window);
898 active = gtk_toggle_action_get_active (toggle_action);
900 empathy_chat_set_show_contacts (priv->current_chat, active);
904 got_contact_cb (TpConnection *connection,
905 EmpathyContact *contact,
910 EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data);
913 DEBUG ("Failed: %s", error->message);
916 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
917 contact, _("Inviting you to this room"));
922 chat_window_invite_participant_activate_cb (GtkAction *action,
923 EmpathyChatWindow *window)
925 EmpathyChatWindowPriv *priv;
927 EmpathyTpChat *tp_chat;
932 priv = GET_PRIV (window);
934 g_return_if_fail (priv->current_chat != NULL);
936 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
937 channel = empathy_tp_chat_get_channel (tp_chat);
938 account = empathy_chat_get_account (priv->current_chat);
940 dialog = empathy_invite_participant_dialog_new (
941 GTK_WINDOW (priv->dialog), account);
942 gtk_widget_show (dialog);
944 response = gtk_dialog_run (GTK_DIALOG (dialog));
946 if (response == GTK_RESPONSE_ACCEPT) {
947 TpConnection *connection;
950 id = empathy_contact_selector_dialog_get_selected (
951 EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL, NULL);
952 if (EMP_STR_EMPTY (id)) goto out;
954 connection = tp_channel_borrow_connection (channel);
955 empathy_tp_contact_factory_get_from_id (connection, id,
956 got_contact_cb, tp_chat, NULL, NULL);
960 gtk_widget_destroy (dialog);
964 chat_window_close_activate_cb (GtkAction *action,
965 EmpathyChatWindow *window)
967 EmpathyChatWindowPriv *priv;
969 priv = GET_PRIV (window);
971 g_return_if_fail (priv->current_chat != NULL);
973 empathy_chat_window_remove_chat (window, priv->current_chat);
977 chat_window_edit_activate_cb (GtkAction *action,
978 EmpathyChatWindow *window)
980 EmpathyChatWindowPriv *priv;
981 GtkClipboard *clipboard;
982 GtkTextBuffer *buffer;
983 gboolean text_available;
985 priv = GET_PRIV (window);
987 g_return_if_fail (priv->current_chat != NULL);
989 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
990 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
991 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
992 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
996 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
997 if (gtk_text_buffer_get_has_selection (buffer)) {
998 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
999 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1003 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1005 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1006 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1009 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1010 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1011 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1015 chat_window_cut_activate_cb (GtkAction *action,
1016 EmpathyChatWindow *window)
1018 EmpathyChatWindowPriv *priv;
1020 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1022 priv = GET_PRIV (window);
1024 empathy_chat_cut (priv->current_chat);
1028 chat_window_copy_activate_cb (GtkAction *action,
1029 EmpathyChatWindow *window)
1031 EmpathyChatWindowPriv *priv;
1033 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1035 priv = GET_PRIV (window);
1037 empathy_chat_copy (priv->current_chat);
1041 chat_window_paste_activate_cb (GtkAction *action,
1042 EmpathyChatWindow *window)
1044 EmpathyChatWindowPriv *priv;
1046 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1048 priv = GET_PRIV (window);
1050 empathy_chat_paste (priv->current_chat);
1054 chat_window_find_activate_cb (GtkAction *action,
1055 EmpathyChatWindow *window)
1057 EmpathyChatWindowPriv *priv;
1059 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1061 priv = GET_PRIV (window);
1063 empathy_chat_find (priv->current_chat);
1067 chat_window_tabs_next_activate_cb (GtkAction *action,
1068 EmpathyChatWindow *window)
1070 EmpathyChatWindowPriv *priv;
1072 gint index_, numPages;
1073 gboolean wrap_around;
1075 priv = GET_PRIV (window);
1077 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1078 &wrap_around, NULL);
1080 chat = priv->current_chat;
1081 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1082 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1084 if (index_ == (numPages - 1) && wrap_around) {
1085 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1089 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1093 chat_window_tabs_previous_activate_cb (GtkAction *action,
1094 EmpathyChatWindow *window)
1096 EmpathyChatWindowPriv *priv;
1098 gint index_, numPages;
1099 gboolean wrap_around;
1101 priv = GET_PRIV (window);
1103 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1104 &wrap_around, NULL);
1106 chat = priv->current_chat;
1107 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1108 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1110 if (index_ <= 0 && wrap_around) {
1111 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1115 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1119 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1120 EmpathyChatWindow *window)
1122 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1123 empathy_chat_manager_undo_closed_chat (priv->chat_manager);
1127 chat_window_tabs_left_activate_cb (GtkAction *action,
1128 EmpathyChatWindow *window)
1130 EmpathyChatWindowPriv *priv;
1132 gint index_, num_pages;
1134 priv = GET_PRIV (window);
1136 chat = priv->current_chat;
1137 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1142 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1146 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1147 chat_window_menu_context_update (priv, num_pages);
1151 chat_window_tabs_right_activate_cb (GtkAction *action,
1152 EmpathyChatWindow *window)
1154 EmpathyChatWindowPriv *priv;
1156 gint index_, num_pages;
1158 priv = GET_PRIV (window);
1160 chat = priv->current_chat;
1161 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1163 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1167 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1168 chat_window_menu_context_update (priv, num_pages);
1171 static EmpathyChatWindow *
1172 empathy_chat_window_new (void)
1174 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1178 chat_window_detach_activate_cb (GtkAction *action,
1179 EmpathyChatWindow *window)
1181 EmpathyChatWindowPriv *priv;
1182 EmpathyChatWindow *new_window;
1185 priv = GET_PRIV (window);
1187 chat = priv->current_chat;
1188 new_window = empathy_chat_window_new ();
1190 empathy_chat_window_move_chat (window, new_window, chat);
1192 priv = GET_PRIV (new_window);
1193 gtk_widget_show (priv->dialog);
1197 chat_window_help_contents_activate_cb (GtkAction *action,
1198 EmpathyChatWindow *window)
1200 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1202 empathy_url_show (priv->dialog, "ghelp:empathy");
1206 chat_window_help_about_activate_cb (GtkAction *action,
1207 EmpathyChatWindow *window)
1209 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1211 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1215 chat_window_delete_event_cb (GtkWidget *dialog,
1217 EmpathyChatWindow *window)
1219 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1221 DEBUG ("Delete event received");
1223 g_object_ref (window);
1224 while (priv->chats) {
1225 empathy_chat_window_remove_chat (window, priv->chats->data);
1227 g_object_unref (window);
1233 chat_window_composing_cb (EmpathyChat *chat,
1234 gboolean is_composing,
1235 EmpathyChatWindow *window)
1237 EmpathyChatWindowPriv *priv;
1239 priv = GET_PRIV (window);
1241 if (is_composing && !g_list_find (priv->chats_composing, chat)) {
1242 priv->chats_composing = g_list_prepend (priv->chats_composing, chat);
1244 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1247 chat_window_update_chat_tab (chat);
1251 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1254 EmpathyChatWindowPriv *priv;
1256 priv = GET_PRIV (window);
1258 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1262 chat_window_notification_closed_cb (NotifyNotification *notify,
1263 EmpathyChatWindow *self)
1265 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1267 g_object_unref (notify);
1268 if (priv->notification == notify) {
1269 priv->notification = NULL;
1274 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1275 EmpathyMessage *message,
1278 EmpathyContact *sender;
1279 const gchar *header;
1283 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1284 gboolean res, has_x_canonical_append;
1285 NotifyNotification *notification = priv->notification;
1287 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1290 GSettings *gsettings = g_settings_new (
1291 EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
1293 res = g_settings_get_boolean (gsettings,
1294 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1296 g_object_unref (gsettings);
1303 sender = empathy_message_get_sender (message);
1304 header = empathy_contact_get_alias (sender);
1305 body = empathy_message_get_body (message);
1306 escaped = g_markup_escape_text (body, -1);
1307 has_x_canonical_append = empathy_notify_manager_has_capability (
1308 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1310 if (notification != NULL && !has_x_canonical_append) {
1311 /* if the notification server supports x-canonical-append, it is
1312 better to not use notify_notification_update to avoid
1313 overwriting the current notification message */
1314 notify_notification_update (notification,
1315 header, escaped, NULL);
1317 /* if the notification server supports x-canonical-append,
1318 the hint will be added, so that the message from the
1319 just created notification will be automatically appended
1320 to an existing notification with the same title.
1321 In this way the previous message will not be lost: the new
1322 message will appear below it, in the same notification */
1323 notification = notify_notification_new (header, escaped, NULL, NULL);
1325 if (priv->notification == NULL) {
1326 priv->notification = notification;
1329 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1331 tp_g_signal_connect_object (notification, "closed",
1332 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1334 if (has_x_canonical_append) {
1335 notify_notification_set_hint_string (notification,
1336 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
1340 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1341 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1343 if (pixbuf != NULL) {
1344 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1345 g_object_unref (pixbuf);
1348 notify_notification_show (notification, NULL);
1354 chat_window_set_highlight_room_tab_label (EmpathyChat *chat)
1359 if (!empathy_chat_is_room (chat))
1362 markup = g_markup_printf_escaped (
1363 "<span color=\"red\" weight=\"bold\">%s</span>",
1364 empathy_chat_get_name (chat));
1366 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1367 gtk_label_set_markup (GTK_LABEL (widget), markup);
1372 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1374 EmpathyChatWindowPriv *priv;
1377 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1379 priv = GET_PRIV (window);
1381 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1387 chat_window_new_message_cb (EmpathyChat *chat,
1388 EmpathyMessage *message,
1389 EmpathyChatWindow *window)
1391 EmpathyChatWindowPriv *priv;
1393 gboolean needs_urgency;
1394 EmpathyContact *sender;
1396 priv = GET_PRIV (window);
1398 has_focus = empathy_chat_window_has_focus (window);
1400 /* - if we're the sender, we play the sound if it's specified in the
1401 * preferences and we're not away.
1402 * - if we receive a message, we play the sound if it's specified in the
1403 * preferences and the window does not have focus on the chat receiving
1407 sender = empathy_message_get_sender (message);
1409 if (empathy_contact_is_user (sender)) {
1410 empathy_sound_play (GTK_WIDGET (priv->dialog),
1411 EMPATHY_SOUND_MESSAGE_OUTGOING);
1414 if (has_focus && priv->current_chat == chat) {
1415 /* window and tab are focused so consider the message to be read */
1417 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1418 empathy_chat_messages_read (chat);
1422 if (!g_list_find (priv->chats_new_msg, chat)) {
1423 priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
1424 chat_window_update_chat_tab (chat);
1427 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1428 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1429 * an unamed MUC (msn-like).
1430 * In case of a MUC, we set urgency if either:
1431 * a) the chatroom's always_urgent property is TRUE
1432 * b) the message contains our alias
1434 if (empathy_chat_is_room (chat) ||
1435 empathy_chat_get_remote_contact (chat) == NULL) {
1438 EmpathyChatroom *chatroom;
1440 account = empathy_chat_get_account (chat);
1441 room = empathy_chat_get_id (chat);
1443 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1446 if (empathy_chatroom_is_always_urgent (chatroom)) {
1447 needs_urgency = TRUE;
1449 needs_urgency = empathy_message_should_highlight (message);
1452 needs_urgency = TRUE;
1455 if (needs_urgency) {
1457 chat_window_set_urgency_hint (window, TRUE);
1458 chat_window_set_highlight_room_tab_label (chat);
1461 empathy_sound_play (GTK_WIDGET (priv->dialog),
1462 EMPATHY_SOUND_MESSAGE_INCOMING);
1463 chat_window_show_or_update_notification (window, message, chat);
1466 /* update the number of unread messages and the window icon */
1467 chat_window_title_update (priv);
1468 chat_window_icon_update (priv);
1471 static GtkNotebook *
1472 chat_window_detach_hook (GtkNotebook *source,
1478 EmpathyChatWindowPriv *priv;
1479 EmpathyChatWindow *window, *new_window;
1482 chat = EMPATHY_CHAT (page);
1483 window = chat_window_find_chat (chat);
1485 new_window = empathy_chat_window_new ();
1486 priv = GET_PRIV (new_window);
1488 DEBUG ("Detach hook called");
1490 empathy_chat_window_move_chat (window, new_window, chat);
1492 gtk_widget_show (priv->dialog);
1493 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1499 chat_window_page_switched_cb (GtkNotebook *notebook,
1500 gpointer ignore, /* see note below */
1502 EmpathyChatWindow *window)
1504 EmpathyChatWindowPriv *priv;
1508 DEBUG ("Page switched");
1510 priv = GET_PRIV (window);
1512 /* N.B. in GTK+ 3 child is passed as the first argument to the signal,
1513 * but we can't use that while trying to retain GTK+ 2.x compatibility.
1515 child = gtk_notebook_get_nth_page (notebook, page_num);
1516 chat = EMPATHY_CHAT (child);
1518 if (priv->page_added) {
1519 priv->page_added = FALSE;
1520 empathy_chat_scroll_down (chat);
1522 else if (priv->current_chat == chat) {
1526 priv->current_chat = chat;
1527 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1528 empathy_chat_messages_read (chat);
1530 chat_window_update_chat_tab (chat);
1534 chat_window_page_added_cb (GtkNotebook *notebook,
1537 EmpathyChatWindow *window)
1539 EmpathyChatWindowPriv *priv;
1542 priv = GET_PRIV (window);
1544 /* If we just received DND to the same window, we don't want
1545 * to do anything here like removing the tab and then readding
1546 * it, so we return here and in "page-added".
1548 if (priv->dnd_same_window) {
1549 DEBUG ("Page added (back to the same window)");
1550 priv->dnd_same_window = FALSE;
1554 DEBUG ("Page added");
1556 /* Get chat object */
1557 chat = EMPATHY_CHAT (child);
1559 /* Connect chat signals for this window */
1560 g_signal_connect (chat, "composing",
1561 G_CALLBACK (chat_window_composing_cb),
1563 g_signal_connect (chat, "new-message",
1564 G_CALLBACK (chat_window_new_message_cb),
1566 g_signal_connect (chat, "notify::tp-chat",
1567 G_CALLBACK (chat_window_update_chat_tab),
1570 /* Set flag so we know to perform some special operations on
1571 * switch page due to the new page being added.
1573 priv->page_added = TRUE;
1575 /* Get list of chats up to date */
1576 priv->chats = g_list_append (priv->chats, chat);
1578 chat_window_update_chat_tab (chat);
1582 chat_window_page_removed_cb (GtkNotebook *notebook,
1585 EmpathyChatWindow *window)
1587 EmpathyChatWindowPriv *priv;
1590 priv = GET_PRIV (window);
1592 /* If we just received DND to the same window, we don't want
1593 * to do anything here like removing the tab and then readding
1594 * it, so we return here and in "page-added".
1596 if (priv->dnd_same_window) {
1597 DEBUG ("Page removed (and will be readded to same window)");
1601 DEBUG ("Page removed");
1603 /* Get chat object */
1604 chat = EMPATHY_CHAT (child);
1606 /* Disconnect all signal handlers for this chat and this window */
1607 g_signal_handlers_disconnect_by_func (chat,
1608 G_CALLBACK (chat_window_composing_cb),
1610 g_signal_handlers_disconnect_by_func (chat,
1611 G_CALLBACK (chat_window_new_message_cb),
1613 g_signal_handlers_disconnect_by_func (chat,
1614 G_CALLBACK (chat_window_update_chat_tab),
1617 /* Keep list of chats up to date */
1618 priv->chats = g_list_remove (priv->chats, chat);
1619 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1620 empathy_chat_messages_read (chat);
1621 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1623 if (priv->chats == NULL) {
1624 g_object_unref (window);
1626 chat_window_update (window);
1631 chat_window_focus_in_event_cb (GtkWidget *widget,
1633 EmpathyChatWindow *window)
1635 EmpathyChatWindowPriv *priv;
1637 priv = GET_PRIV (window);
1639 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
1640 empathy_chat_messages_read (priv->current_chat);
1642 chat_window_set_urgency_hint (window, FALSE);
1644 /* Update the title, since we now mark all unread messages as read. */
1645 chat_window_update_chat_tab (priv->current_chat);
1651 chat_window_drag_drop (GtkWidget *widget,
1652 GdkDragContext *context,
1656 EmpathyChatWindow *window)
1659 EmpathyChatWindowPriv *priv;
1661 priv = GET_PRIV (window);
1663 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1664 if (target == GDK_NONE)
1665 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1667 if (target != GDK_NONE) {
1668 gtk_drag_get_data (widget, context, target, time_);
1676 chat_window_drag_motion (GtkWidget *widget,
1677 GdkDragContext *context,
1681 EmpathyChatWindow *window)
1684 EmpathyChatWindowPriv *priv;
1686 priv = GET_PRIV (window);
1688 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1689 if (target != GDK_NONE) {
1690 /* This is a file drag. Ensure the contact is online and set the
1691 drag type to COPY. Note that it's possible that the tab will
1692 be switched by GTK+ after a timeout from drag_motion without
1693 getting another drag_motion to disable the drop. You have
1694 to hold your mouse really still.
1696 EmpathyContact *contact;
1698 priv = GET_PRIV (window);
1699 contact = empathy_chat_get_remote_contact (priv->current_chat);
1700 /* contact is NULL for multi-user chats. We don't do
1701 * file transfers to MUCs. We also don't send files
1702 * to offline contacts or contacts that don't support
1705 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1706 gdk_drag_status (context, 0, time_);
1709 if (!(empathy_contact_get_capabilities (contact)
1710 & EMPATHY_CAPABILITIES_FT)) {
1711 gdk_drag_status (context, 0, time_);
1714 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1718 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1719 if (target != GDK_NONE) {
1720 /* This is a drag of a contact from a contact list. Set to COPY.
1721 FIXME: If this drag is to a MUC window, it invites the user.
1722 Otherwise, it opens a chat. Should we use a different drag
1723 type for invites? Should we allow ASK?
1725 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1733 chat_window_drag_data_received (GtkWidget *widget,
1734 GdkDragContext *context,
1737 GtkSelectionData *selection,
1740 EmpathyChatWindow *window)
1742 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1743 EmpathyChat *chat = NULL;
1744 EmpathyChatWindow *old_window;
1745 TpAccount *account = NULL;
1746 TpAccountManager *account_manager;
1749 const gchar *account_id;
1750 const gchar *contact_id;
1752 id = (const gchar*) gtk_selection_data_get_data (selection);
1754 /* FIXME: Perhaps should be sure that the account manager is
1755 * prepared before calling _ensure_account on it. */
1756 account_manager = tp_account_manager_dup ();
1758 DEBUG ("DND contact from roster with id:'%s'", id);
1760 strv = g_strsplit (id, ":", 2);
1761 if (g_strv_length (strv) == 2) {
1762 account_id = strv[0];
1763 contact_id = strv[1];
1765 tp_account_manager_ensure_account (account_manager, account_id);
1766 if (account != NULL)
1767 chat = empathy_chat_window_find_chat (account, contact_id);
1770 if (account == NULL) {
1772 gtk_drag_finish (context, FALSE, FALSE, time_);
1777 empathy_dispatcher_chat_with_contact_id (
1778 account, contact_id, gtk_get_current_event_time ());
1783 g_object_unref (account_manager);
1786 old_window = chat_window_find_chat (chat);
1788 if (old_window == window) {
1789 gtk_drag_finish (context, TRUE, FALSE, time_);
1793 empathy_chat_window_move_chat (old_window, window, chat);
1795 empathy_chat_window_add_chat (window, chat);
1798 /* Added to take care of any outstanding chat events */
1799 empathy_chat_window_present_chat (chat,
1800 TP_USER_ACTION_TIME_NOT_USER_ACTION);
1802 /* We should return TRUE to remove the data when doing
1803 * GDK_ACTION_MOVE, but we don't here otherwise it has
1804 * weird consequences, and we handle that internally
1805 * anyway with add_chat () and remove_chat ().
1807 gtk_drag_finish (context, TRUE, FALSE, time_);
1809 else if (info == DND_DRAG_TYPE_URI_LIST) {
1810 EmpathyChatWindowPriv *priv;
1811 EmpathyContact *contact;
1814 priv = GET_PRIV (window);
1815 contact = empathy_chat_get_remote_contact (priv->current_chat);
1817 /* contact is NULL when current_chat is a multi-user chat.
1818 * We don't do file transfers to MUCs, so just cancel the drag.
1820 if (contact == NULL) {
1821 gtk_drag_finish (context, TRUE, FALSE, time_);
1825 data = (const gchar *) gtk_selection_data_get_data (selection);
1826 empathy_send_file_from_uri_list (contact, data);
1828 gtk_drag_finish (context, TRUE, FALSE, time_);
1830 else if (info == DND_DRAG_TYPE_TAB) {
1832 EmpathyChatWindow *old_window = NULL;
1836 chat = (void *) gtk_selection_data_get_data (selection);
1837 old_window = chat_window_find_chat (*chat);
1840 EmpathyChatWindowPriv *priv;
1842 priv = GET_PRIV (window);
1843 priv->dnd_same_window = (old_window == window);
1844 DEBUG ("DND tab (within same window: %s)",
1845 priv->dnd_same_window ? "Yes" : "No");
1848 DEBUG ("DND from unknown source");
1849 gtk_drag_finish (context, FALSE, FALSE, time_);
1854 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1855 guint num_chats_in_manager,
1856 EmpathyChatWindow *window)
1858 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1860 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1861 num_chats_in_manager > 0);
1865 chat_window_finalize (GObject *object)
1867 EmpathyChatWindow *window;
1868 EmpathyChatWindowPriv *priv;
1870 window = EMPATHY_CHAT_WINDOW (object);
1871 priv = GET_PRIV (window);
1873 DEBUG ("Finalized: %p", object);
1875 g_object_unref (priv->ui_manager);
1876 g_object_unref (priv->chatroom_manager);
1877 g_object_unref (priv->notify_mgr);
1879 if (priv->notification != NULL) {
1880 notify_notification_close (priv->notification, NULL);
1881 priv->notification = NULL;
1884 if (priv->contact_targets) {
1885 gtk_target_list_unref (priv->contact_targets);
1887 if (priv->file_targets) {
1888 gtk_target_list_unref (priv->file_targets);
1891 if (priv->chat_manager) {
1892 g_signal_handler_disconnect (priv->chat_manager,
1893 priv->chat_manager_chats_changed_id);
1894 g_object_unref (priv->chat_manager);
1895 priv->chat_manager = NULL;
1898 chat_windows = g_list_remove (chat_windows, window);
1899 gtk_widget_destroy (priv->dialog);
1901 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1905 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1907 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1909 object_class->finalize = chat_window_finalize;
1911 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1913 /* Set up a style for the close button with no focus padding. */
1914 gtk_rc_parse_string (
1915 "style \"empathy-close-button-style\"\n"
1917 " GtkWidget::focus-padding = 0\n"
1921 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1923 gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1927 empathy_chat_window_init (EmpathyChatWindow *window)
1930 GtkAccelGroup *accel_group;
1935 GtkWidget *chat_vbox;
1937 EmpathySmileyManager *smiley_manager;
1938 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1939 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1941 window->priv = priv;
1942 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1943 gui = empathy_builder_get_file (filename,
1944 "chat_window", &priv->dialog,
1945 "chat_vbox", &chat_vbox,
1946 "ui_manager", &priv->ui_manager,
1947 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1948 "menu_conv_favorite", &priv->menu_conv_favorite,
1949 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
1950 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1951 "menu_edit_cut", &priv->menu_edit_cut,
1952 "menu_edit_copy", &priv->menu_edit_copy,
1953 "menu_edit_paste", &priv->menu_edit_paste,
1954 "menu_edit_find", &priv->menu_edit_find,
1955 "menu_tabs_next", &priv->menu_tabs_next,
1956 "menu_tabs_prev", &priv->menu_tabs_prev,
1957 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1958 "menu_tabs_left", &priv->menu_tabs_left,
1959 "menu_tabs_right", &priv->menu_tabs_right,
1960 "menu_tabs_detach", &priv->menu_tabs_detach,
1964 empathy_builder_connect (gui, window,
1965 "menu_conv", "activate", chat_window_conv_activate_cb,
1966 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1967 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1968 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
1969 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1970 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1971 "menu_conv_close", "activate", chat_window_close_activate_cb,
1972 "menu_edit", "activate", chat_window_edit_activate_cb,
1973 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1974 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1975 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1976 "menu_edit_find", "activate", chat_window_find_activate_cb,
1977 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1978 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1979 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1980 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1981 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1982 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1983 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1984 "menu_help_about", "activate", chat_window_help_about_activate_cb,
1987 g_object_ref (priv->ui_manager);
1988 g_object_unref (gui);
1990 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1992 priv->notebook = gtk_notebook_new ();
1993 gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
1994 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
1995 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
1996 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1997 gtk_widget_show (priv->notebook);
2000 accel_group = gtk_accel_group_new ();
2001 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2003 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2004 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2007 gtk_accel_group_connect (accel_group,
2014 g_object_unref (accel_group);
2016 /* Set up drag target lists */
2017 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2018 G_N_ELEMENTS (drag_types_dest_contact));
2019 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2020 G_N_ELEMENTS (drag_types_dest_file));
2022 /* Set up smiley menu */
2023 smiley_manager = empathy_smiley_manager_dup_singleton ();
2024 submenu = empathy_smiley_menu_new (smiley_manager,
2025 chat_window_insert_smiley_activate_cb,
2027 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2028 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2029 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2030 g_object_unref (smiley_manager);
2032 /* Set up signals we can't do with ui file since we may need to
2033 * block/unblock them at some later stage.
2036 g_signal_connect (priv->dialog,
2038 G_CALLBACK (chat_window_delete_event_cb),
2040 g_signal_connect (priv->dialog,
2042 G_CALLBACK (chat_window_focus_in_event_cb),
2044 g_signal_connect_after (priv->notebook,
2046 G_CALLBACK (chat_window_page_switched_cb),
2048 g_signal_connect (priv->notebook,
2050 G_CALLBACK (chat_window_page_added_cb),
2052 g_signal_connect (priv->notebook,
2054 G_CALLBACK (chat_window_page_removed_cb),
2057 /* Set up drag and drop */
2058 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2059 GTK_DEST_DEFAULT_HIGHLIGHT,
2061 G_N_ELEMENTS (drag_types_dest),
2062 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2064 /* connect_after to allow GtkNotebook's built-in tab switching */
2065 g_signal_connect_after (priv->notebook,
2067 G_CALLBACK (chat_window_drag_motion),
2069 g_signal_connect (priv->notebook,
2070 "drag-data-received",
2071 G_CALLBACK (chat_window_drag_data_received),
2073 g_signal_connect (priv->notebook,
2075 G_CALLBACK (chat_window_drag_drop),
2078 chat_windows = g_list_prepend (chat_windows, window);
2080 /* Set up private details */
2082 priv->chats_new_msg = NULL;
2083 priv->chats_composing = NULL;
2084 priv->current_chat = NULL;
2085 priv->notification = NULL;
2087 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2089 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2090 priv->chat_manager_chats_changed_id =
2091 g_signal_connect (priv->chat_manager, "chats-changed",
2092 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2095 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2096 empathy_chat_manager_get_num_chats (priv->chat_manager),
2101 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2103 EmpathyChatWindowPriv *priv;
2105 g_return_val_if_fail (window != NULL, NULL);
2107 priv = GET_PRIV (window);
2109 return priv->dialog;
2112 /* Returns the window to open a new tab in if there is only one window
2113 * visble, otherwise, returns NULL indicating that a new window should
2116 static EmpathyChatWindow *
2117 empathy_chat_window_get_default (gboolean room)
2119 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2121 gboolean separate_windows = TRUE;
2123 separate_windows = g_settings_get_boolean (gsettings,
2124 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2126 g_object_unref (gsettings);
2128 if (separate_windows) {
2129 /* Always create a new window */
2133 for (l = chat_windows; l; l = l->next) {
2134 EmpathyChatWindowPriv *priv;
2135 EmpathyChatWindow *chat_window;
2138 chat_window = l->data;
2139 priv = GET_PRIV (chat_window);
2141 dialog = empathy_chat_window_get_dialog (chat_window);
2142 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
2143 guint nb_rooms, nb_private;
2144 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2146 /* Skip the window if there aren't any rooms in it */
2147 if (room && nb_rooms == 0)
2150 /* Skip the window if there aren't any 1-1 chats in it */
2151 if (!room && nb_private == 0)
2154 /* Found a visible window on this desktop */
2163 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2166 EmpathyChatWindowPriv *priv;
2168 GtkWidget *popup_label;
2170 GValue value = { 0, };
2172 g_return_if_fail (window != NULL);
2173 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2175 priv = GET_PRIV (window);
2177 /* Reference the chat object */
2178 g_object_ref (chat);
2180 /* If this window has just been created, position it */
2181 if (priv->chats == NULL) {
2182 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2183 const gchar *name = "chat-window";
2184 gboolean separate_windows;
2186 separate_windows = g_settings_get_boolean (gsettings,
2187 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2189 g_object_unref (gsettings);
2191 if (separate_windows) {
2192 name = empathy_chat_get_id (chat);
2194 else if (empathy_chat_is_room (chat)) {
2195 name = "room-window";
2198 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2201 child = GTK_WIDGET (chat);
2202 label = chat_window_create_label (window, chat, TRUE);
2203 popup_label = chat_window_create_label (window, chat, FALSE);
2204 gtk_widget_show (child);
2206 g_signal_connect (chat, "notify::name",
2207 G_CALLBACK (chat_window_chat_notify_cb),
2209 g_signal_connect (chat, "notify::subject",
2210 G_CALLBACK (chat_window_chat_notify_cb),
2212 g_signal_connect (chat, "notify::remote-contact",
2213 G_CALLBACK (chat_window_chat_notify_cb),
2215 chat_window_chat_notify_cb (chat);
2217 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2218 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2219 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2220 g_value_init (&value, G_TYPE_BOOLEAN);
2221 g_value_set_boolean (&value, TRUE);
2222 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2223 child, "tab-expand" , &value);
2224 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2225 child, "tab-fill" , &value);
2226 g_value_unset (&value);
2228 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2232 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2235 EmpathyChatWindowPriv *priv;
2237 EmpathyContact *remote_contact;
2238 EmpathyChatManager *chat_manager;
2240 g_return_if_fail (window != NULL);
2241 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2243 priv = GET_PRIV (window);
2245 g_signal_handlers_disconnect_by_func (chat,
2246 chat_window_chat_notify_cb,
2248 remote_contact = g_object_get_data (G_OBJECT (chat),
2249 "chat-window-remote-contact");
2250 if (remote_contact) {
2251 g_signal_handlers_disconnect_by_func (remote_contact,
2252 chat_window_update_chat_tab,
2256 chat_manager = empathy_chat_manager_dup_singleton ();
2257 empathy_chat_manager_closed_chat (chat_manager, chat);
2258 g_object_unref (chat_manager);
2260 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2262 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2264 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2266 g_object_unref (chat);
2270 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2271 EmpathyChatWindow *new_window,
2276 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2277 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2278 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2280 widget = GTK_WIDGET (chat);
2282 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2283 G_OBJECT (widget)->ref_count);
2285 /* We reference here to make sure we don't loose the widget
2286 * and the EmpathyChat object during the move.
2288 g_object_ref (chat);
2289 g_object_ref (widget);
2291 empathy_chat_window_remove_chat (old_window, chat);
2292 empathy_chat_window_add_chat (new_window, chat);
2294 g_object_unref (widget);
2295 g_object_unref (chat);
2299 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2302 EmpathyChatWindowPriv *priv;
2305 g_return_if_fail (window != NULL);
2306 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2308 priv = GET_PRIV (window);
2310 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2312 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2317 empathy_chat_window_find_chat (TpAccount *account,
2322 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2324 for (l = chat_windows; l; l = l->next) {
2325 EmpathyChatWindowPriv *priv;
2326 EmpathyChatWindow *window;
2330 priv = GET_PRIV (window);
2332 for (ll = priv->chats; ll; ll = ll->next) {
2337 if (account == empathy_chat_get_account (chat) &&
2338 !tp_strdiff (id, empathy_chat_get_id (chat))) {
2348 empathy_chat_window_present_chat (EmpathyChat *chat,
2351 EmpathyChatWindow *window;
2352 EmpathyChatWindowPriv *priv;
2353 guint32 x_timestamp;
2355 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2357 window = chat_window_find_chat (chat);
2359 /* If the chat has no window, create one */
2360 if (window == NULL) {
2361 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2363 window = empathy_chat_window_new ();
2364 gtk_widget_show_all (GET_PRIV (window)->dialog);
2367 empathy_chat_window_add_chat (window, chat);
2370 /* Don't force the window to show itself when it wasn't
2371 * an action by the user
2373 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2376 priv = GET_PRIV (window);
2378 if (x_timestamp != GDK_CURRENT_TIME) {
2379 /* Don't present or switch tab if the action was earlier than the
2380 * last actions X time, accounting for overflow and the first ever
2383 if (priv->x_user_action_time != 0
2384 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2387 priv->x_user_action_time = x_timestamp;
2390 empathy_chat_window_switch_to_chat (window, chat);
2391 empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2394 gtk_widget_grab_focus (chat->input_text_view);
2398 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2402 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2404 guint _nb_rooms = 0, _nb_private = 0;
2406 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2407 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2413 if (nb_rooms != NULL)
2414 *nb_rooms = _nb_rooms;
2415 if (nb_private != NULL)
2416 *nb_private = _nb_private;