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-2010 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-manager.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 gboolean dnd_same_window;
79 EmpathyChatroomManager *chatroom_manager;
80 EmpathyNotifyManager *notify_mgr;
83 NotifyNotification *notification;
85 GtkTargetList *contact_targets;
86 GtkTargetList *file_targets;
88 EmpathyChatManager *chat_manager;
89 gulong chat_manager_chats_changed_id;
92 GtkUIManager *ui_manager;
93 GtkAction *menu_conv_insert_smiley;
94 GtkAction *menu_conv_favorite;
95 GtkAction *menu_conv_always_urgent;
96 GtkAction *menu_conv_toggle_contacts;
98 GtkAction *menu_edit_cut;
99 GtkAction *menu_edit_copy;
100 GtkAction *menu_edit_paste;
101 GtkAction *menu_edit_find;
103 GtkAction *menu_tabs_next;
104 GtkAction *menu_tabs_prev;
105 GtkAction *menu_tabs_undo_close_tab;
106 GtkAction *menu_tabs_left;
107 GtkAction *menu_tabs_right;
108 GtkAction *menu_tabs_detach;
110 /* Last user action time we acted upon to show a tab */
111 guint32 x_user_action_time;
113 GSettings *gsettings_chat;
114 GSettings *gsettings_notif;
115 GSettings *gsettings_ui;
117 EmpathySoundManager *sound_mgr;
118 } EmpathyChatWindowPriv;
120 static GList *chat_windows = NULL;
122 static const guint tab_accel_keys[] = {
123 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
124 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
128 DND_DRAG_TYPE_CONTACT_ID,
129 DND_DRAG_TYPE_URI_LIST,
133 static const GtkTargetEntry drag_types_dest[] = {
134 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
135 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
136 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
137 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
140 static const GtkTargetEntry drag_types_dest_contact[] = {
141 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
144 static const GtkTargetEntry drag_types_dest_file[] = {
145 /* must be first to be prioritized, in order to receive the
146 * note's file path from Tomboy instead of an URI */
147 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
148 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
151 static void chat_window_update (EmpathyChatWindow *window);
153 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
156 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
159 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
160 EmpathyChatWindow *new_window,
163 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
167 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
170 chat_window_accel_cb (GtkAccelGroup *accelgroup,
174 EmpathyChatWindow *window)
176 EmpathyChatWindowPriv *priv;
180 priv = GET_PRIV (window);
182 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
183 if (tab_accel_keys[i] == key) {
190 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
194 static EmpathyChatWindow *
195 chat_window_find_chat (EmpathyChat *chat)
197 EmpathyChatWindowPriv *priv;
200 for (l = chat_windows; l; l = l->next) {
201 priv = GET_PRIV (l->data);
202 ll = g_list_find (priv->chats, chat);
212 chat_window_close_clicked_cb (GtkAction *action,
215 EmpathyChatWindow *window;
217 window = chat_window_find_chat (chat);
218 empathy_chat_window_remove_chat (window, chat);
222 chat_tab_style_set_cb (GtkWidget *hbox,
223 GtkStyle *previous_style,
227 int char_width, h, w;
228 PangoContext *context;
229 const PangoFontDescription *font_desc;
230 PangoFontMetrics *metrics;
232 button = g_object_get_data (G_OBJECT (user_data),
233 "chat-window-tab-close-button");
234 context = gtk_widget_get_pango_context (hbox);
236 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
237 GTK_STATE_FLAG_NORMAL);
239 metrics = pango_context_get_metrics (context, font_desc,
240 pango_context_get_language (context));
241 char_width = pango_font_metrics_get_approximate_char_width (metrics);
242 pango_font_metrics_unref (metrics);
244 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
245 GTK_ICON_SIZE_MENU, &w, &h);
247 /* Request at least about 12 chars width plus at least space for the status
248 * image and the close button */
249 gtk_widget_set_size_request (hbox,
250 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
252 gtk_widget_set_size_request (button, w, h);
256 chat_window_create_label (EmpathyChatWindow *window,
258 gboolean is_tab_label)
260 EmpathyChatWindowPriv *priv;
262 GtkWidget *name_label;
263 GtkWidget *status_image;
264 GtkWidget *close_button;
265 GtkWidget *close_image;
266 GtkWidget *event_box;
267 GtkWidget *event_box_hbox;
268 PangoAttrList *attr_list;
269 PangoAttribute *attr;
271 priv = GET_PRIV (window);
273 /* The spacing between the button and the label. */
274 hbox = gtk_hbox_new (FALSE, 0);
276 event_box = gtk_event_box_new ();
277 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
279 name_label = gtk_label_new (NULL);
281 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
283 attr_list = pango_attr_list_new ();
284 attr = pango_attr_scale_new (1/1.2);
285 attr->start_index = 0;
286 attr->end_index = -1;
287 pango_attr_list_insert (attr_list, attr);
288 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
289 pango_attr_list_unref (attr_list);
291 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
292 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
293 g_object_set_data (G_OBJECT (chat),
294 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
297 status_image = gtk_image_new ();
299 /* Spacing between the icon and label. */
300 event_box_hbox = gtk_hbox_new (FALSE, 0);
302 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
303 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
305 g_object_set_data (G_OBJECT (chat),
306 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
308 g_object_set_data (G_OBJECT (chat),
309 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
312 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
313 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
316 close_button = gtk_button_new ();
317 gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
318 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
320 /* We don't want focus/keynav for the button to avoid clutter, and
321 * Ctrl-W works anyway.
323 gtk_widget_set_can_focus (close_button, FALSE);
324 gtk_widget_set_can_default (close_button, FALSE);
326 /* Set the name to make the special rc style match. */
327 gtk_widget_set_name (close_button, "empathy-close-button");
329 close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
331 gtk_container_add (GTK_CONTAINER (close_button), close_image);
333 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
335 g_signal_connect (close_button,
337 G_CALLBACK (chat_window_close_clicked_cb),
340 /* React to theme changes and also setup the size correctly. */
341 g_signal_connect (hbox,
343 G_CALLBACK (chat_tab_style_set_cb),
347 gtk_widget_show_all (hbox);
353 _submenu_notify_visible_changed_cb (GObject *object,
357 g_signal_handlers_disconnect_by_func (object,
358 _submenu_notify_visible_changed_cb,
360 chat_window_update (EMPATHY_CHAT_WINDOW (userdata));
364 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
369 gboolean wrap_around;
370 gboolean is_connected;
373 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
374 first_page = (page_num == 0);
375 last_page = (page_num == (num_pages - 1));
376 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
378 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
380 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
382 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
384 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
385 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
386 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
387 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
391 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
392 EmpathyChatWindow *self)
394 EmpathyTpChat *tp_chat;
395 TpConnection *connection;
397 gboolean sensitive = FALSE;
399 g_return_if_fail (priv->current_chat != NULL);
401 action = gtk_ui_manager_get_action (priv->ui_manager,
402 "/chats_menubar/menu_conv/menu_conv_invite_participant");
403 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
405 if (tp_chat != NULL) {
406 connection = empathy_tp_chat_get_connection (tp_chat);
408 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
409 (tp_connection_get_status (connection, NULL) ==
410 TP_CONNECTION_STATUS_CONNECTED);
413 gtk_action_set_sensitive (action, sensitive);
417 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
418 EmpathyChatWindow *window)
420 GtkWidget *menu, *submenu, *orig_submenu;
422 menu = gtk_ui_manager_get_widget (priv->ui_manager,
423 "/chats_menubar/menu_contact");
424 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
426 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
427 submenu = empathy_chat_get_contact_menu (priv->current_chat);
428 if (submenu != NULL) {
429 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
430 gtk_widget_show (menu);
431 gtk_widget_set_sensitive (menu, TRUE);
434 gtk_widget_set_sensitive (menu, FALSE);
437 tp_g_signal_connect_object (orig_submenu,
439 (GCallback)_submenu_notify_visible_changed_cb,
445 get_all_unread_messages (EmpathyChatWindowPriv *priv)
450 for (l = priv->chats; l != NULL; l = g_list_next (l))
451 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
457 get_window_title_name (EmpathyChatWindowPriv *priv)
459 const gchar *active_name;
461 guint current_unread_msgs;
463 nb_chats = g_list_length (priv->chats);
464 g_assert (nb_chats > 0);
466 active_name = empathy_chat_get_name (priv->current_chat);
468 current_unread_msgs = empathy_chat_get_nb_unread_messages (
473 if (current_unread_msgs == 0)
474 return g_strdup (active_name);
476 return g_strdup_printf (ngettext (
478 "%s (%d unread)", current_unread_msgs),
479 active_name, current_unread_msgs);
481 guint nb_others = nb_chats - 1;
482 guint all_unread_msgs;
484 all_unread_msgs = get_all_unread_messages (priv);
486 if (all_unread_msgs == 0) {
487 /* no unread message */
488 return g_strdup_printf (ngettext (
490 "%s (and %u others)", nb_others),
491 active_name, nb_others);
494 else if (all_unread_msgs == current_unread_msgs) {
495 /* unread messages are in the current tab */
496 return g_strdup_printf (ngettext (
498 "%s (%d unread)", current_unread_msgs),
499 active_name, current_unread_msgs);
502 else if (current_unread_msgs == 0) {
503 /* unread messages are in other tabs */
504 return g_strdup_printf (ngettext (
505 "%s (%d unread from others)",
506 "%s (%d unread from others)",
508 active_name, all_unread_msgs);
512 /* unread messages are in all the tabs */
513 return g_strdup_printf (ngettext (
514 "%s (%d unread from all)",
515 "%s (%d unread from all)",
517 active_name, all_unread_msgs);
523 chat_window_title_update (EmpathyChatWindowPriv *priv)
527 name = get_window_title_name (priv);
528 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
533 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
536 EmpathyContact *remote_contact;
537 gboolean avatar_in_icon;
540 n_chats = g_list_length (priv->chats);
542 /* Update window icon */
544 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
545 EMPATHY_IMAGE_MESSAGE);
547 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
548 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
550 if (n_chats == 1 && avatar_in_icon) {
551 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
552 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
553 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
556 g_object_unref (icon);
559 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
565 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
569 GtkWidget *chat_close_button;
572 if (num_pages == 1) {
573 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
574 chat_close_button = g_object_get_data (G_OBJECT (chat),
575 "chat-window-tab-close-button");
576 gtk_widget_hide (chat_close_button);
578 for (i=0; i<num_pages; i++) {
579 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
580 chat_close_button = g_object_get_data (G_OBJECT (chat),
581 "chat-window-tab-close-button");
582 gtk_widget_show (chat_close_button);
588 chat_window_update (EmpathyChatWindow *window)
590 EmpathyChatWindowPriv *priv = GET_PRIV (window);
593 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
595 /* Update Tab menu */
596 chat_window_menu_context_update (priv,
599 chat_window_conversation_menu_update (priv, window);
601 chat_window_contact_menu_update (priv,
604 chat_window_title_update (priv);
606 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
608 chat_window_close_button_update (priv,
613 append_markup_printf (GString *string,
620 va_start (args, format);
622 tmp = g_markup_vprintf_escaped (format, args);
623 g_string_append (string, tmp);
630 chat_window_update_chat_tab (EmpathyChat *chat)
632 EmpathyChatWindow *window;
633 EmpathyChatWindowPriv *priv;
634 EmpathyContact *remote_contact;
638 const gchar *subject;
639 const gchar *status = NULL;
643 const gchar *icon_name;
644 GtkWidget *tab_image;
645 GtkWidget *menu_image;
647 window = chat_window_find_chat (chat);
651 priv = GET_PRIV (window);
653 /* Get information */
654 name = empathy_chat_get_name (chat);
655 account = empathy_chat_get_account (chat);
656 subject = empathy_chat_get_subject (chat);
657 remote_contact = empathy_chat_get_remote_contact (chat);
659 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
660 name, tp_proxy_get_object_path (account), subject, remote_contact);
662 /* Update tab image */
663 if (empathy_chat_get_tp_chat (chat) == NULL) {
664 /* No TpChat, we are disconnected */
667 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
668 icon_name = EMPATHY_IMAGE_MESSAGE;
670 else if (empathy_chat_is_composing (chat)) {
671 icon_name = EMPATHY_IMAGE_TYPING;
673 else if (remote_contact) {
674 icon_name = empathy_icon_name_for_contact (remote_contact);
676 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
679 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
680 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
681 if (icon_name != NULL) {
682 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
683 gtk_widget_show (tab_image);
684 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
685 gtk_widget_show (menu_image);
687 gtk_widget_hide (tab_image);
688 gtk_widget_hide (menu_image);
691 /* Update tab tooltip */
692 tooltip = g_string_new (NULL);
694 if (remote_contact) {
695 id = empathy_contact_get_id (remote_contact);
696 status = empathy_contact_get_presence_message (remote_contact);
701 append_markup_printf (tooltip,
702 "<b>%s</b><small> (%s)</small>",
704 tp_account_get_display_name (account));
706 if (!EMP_STR_EMPTY (status)) {
707 append_markup_printf (tooltip, "\n<i>%s</i>", status);
711 append_markup_printf (tooltip, "\n<b>%s</b> %s",
712 _("Topic:"), subject);
715 if (empathy_chat_is_composing (chat)) {
716 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
719 markup = g_string_free (tooltip, FALSE);
720 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
721 gtk_widget_set_tooltip_markup (widget, markup);
722 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
723 gtk_widget_set_tooltip_markup (widget, markup);
726 /* Update tab and menu label */
727 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
728 gtk_label_set_text (GTK_LABEL (widget), name);
729 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
730 gtk_label_set_text (GTK_LABEL (widget), name);
732 /* Update the window if it's the current chat */
733 if (priv->current_chat == chat) {
734 chat_window_update (window);
739 chat_window_chat_notify_cb (EmpathyChat *chat)
741 EmpathyContact *old_remote_contact;
742 EmpathyContact *remote_contact = NULL;
744 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
745 remote_contact = empathy_chat_get_remote_contact (chat);
747 if (old_remote_contact != remote_contact) {
748 /* The remote-contact associated with the chat changed, we need
749 * to keep track of any change of that contact and update the
750 * window each time. */
751 if (remote_contact) {
752 g_signal_connect_swapped (remote_contact, "notify",
753 G_CALLBACK (chat_window_update_chat_tab),
756 if (old_remote_contact) {
757 g_signal_handlers_disconnect_by_func (old_remote_contact,
758 chat_window_update_chat_tab,
762 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
763 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
766 chat_window_update_chat_tab (chat);
770 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
771 EmpathySmiley *smiley,
774 EmpathyChatWindowPriv *priv = GET_PRIV (window);
776 GtkTextBuffer *buffer;
779 chat = priv->current_chat;
781 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
782 gtk_text_buffer_get_end_iter (buffer, &iter);
783 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
787 chat_window_conv_activate_cb (GtkAction *action,
788 EmpathyChatWindow *window)
790 EmpathyChatWindowPriv *priv = GET_PRIV (window);
793 EmpathyContact *remote_contact = NULL;
795 /* Favorite room menu */
796 is_room = empathy_chat_is_room (priv->current_chat);
800 gboolean found = FALSE;
801 EmpathyChatroom *chatroom;
803 room = empathy_chat_get_id (priv->current_chat);
804 account = empathy_chat_get_account (priv->current_chat);
805 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
807 if (chatroom != NULL)
808 found = empathy_chatroom_is_favorite (chatroom);
810 DEBUG ("This room %s favorite", found ? "is" : "is not");
811 gtk_toggle_action_set_active (
812 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
814 if (chatroom != NULL)
815 found = empathy_chatroom_is_always_urgent (chatroom);
817 gtk_toggle_action_set_active (
818 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
821 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
822 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
824 /* Show contacts menu */
825 g_object_get (priv->current_chat,
826 "remote-contact", &remote_contact,
827 "show-contacts", &active,
829 if (remote_contact == NULL) {
830 gtk_toggle_action_set_active (
831 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
834 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
835 (remote_contact == NULL));
836 if (remote_contact != NULL) {
837 g_object_unref (remote_contact);
842 chat_window_clear_activate_cb (GtkAction *action,
843 EmpathyChatWindow *window)
845 EmpathyChatWindowPriv *priv = GET_PRIV (window);
847 empathy_chat_clear (priv->current_chat);
851 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
852 EmpathyChatWindow *window)
854 EmpathyChatWindowPriv *priv = GET_PRIV (window);
858 EmpathyChatroom *chatroom;
860 active = gtk_toggle_action_get_active (toggle_action);
861 account = empathy_chat_get_account (priv->current_chat);
862 room = empathy_chat_get_id (priv->current_chat);
864 chatroom = empathy_chatroom_manager_ensure_chatroom (
865 priv->chatroom_manager,
868 empathy_chat_get_name (priv->current_chat));
870 empathy_chatroom_set_favorite (chatroom, active);
871 g_object_unref (chatroom);
875 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
876 EmpathyChatWindow *window)
878 EmpathyChatWindowPriv *priv = GET_PRIV (window);
882 EmpathyChatroom *chatroom;
884 active = gtk_toggle_action_get_active (toggle_action);
885 account = empathy_chat_get_account (priv->current_chat);
886 room = empathy_chat_get_id (priv->current_chat);
888 chatroom = empathy_chatroom_manager_ensure_chatroom (
889 priv->chatroom_manager,
892 empathy_chat_get_name (priv->current_chat));
894 empathy_chatroom_set_always_urgent (chatroom, active);
895 g_object_unref (chatroom);
899 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
900 EmpathyChatWindow *window)
902 EmpathyChatWindowPriv *priv = GET_PRIV (window);
905 active = gtk_toggle_action_get_active (toggle_action);
907 empathy_chat_set_show_contacts (priv->current_chat, active);
911 got_contact_cb (TpConnection *connection,
912 EmpathyContact *contact,
917 EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data);
920 DEBUG ("Failed: %s", error->message);
923 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
924 contact, _("Inviting you to this room"));
929 chat_window_invite_participant_activate_cb (GtkAction *action,
930 EmpathyChatWindow *window)
932 EmpathyChatWindowPriv *priv;
934 EmpathyTpChat *tp_chat;
939 priv = GET_PRIV (window);
941 g_return_if_fail (priv->current_chat != NULL);
943 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
944 channel = empathy_tp_chat_get_channel (tp_chat);
945 account = empathy_chat_get_account (priv->current_chat);
947 dialog = empathy_invite_participant_dialog_new (
948 GTK_WINDOW (priv->dialog), account);
949 gtk_widget_show (dialog);
951 response = gtk_dialog_run (GTK_DIALOG (dialog));
953 if (response == GTK_RESPONSE_ACCEPT) {
954 TpConnection *connection;
957 id = empathy_contact_selector_dialog_get_selected (
958 EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL, NULL);
959 if (EMP_STR_EMPTY (id)) goto out;
961 connection = tp_channel_borrow_connection (channel);
962 empathy_tp_contact_factory_get_from_id (connection, id,
963 got_contact_cb, tp_chat, NULL, NULL);
967 gtk_widget_destroy (dialog);
971 chat_window_close_activate_cb (GtkAction *action,
972 EmpathyChatWindow *window)
974 EmpathyChatWindowPriv *priv;
976 priv = GET_PRIV (window);
978 g_return_if_fail (priv->current_chat != NULL);
980 empathy_chat_window_remove_chat (window, priv->current_chat);
984 chat_window_edit_activate_cb (GtkAction *action,
985 EmpathyChatWindow *window)
987 EmpathyChatWindowPriv *priv;
988 GtkClipboard *clipboard;
989 GtkTextBuffer *buffer;
990 gboolean text_available;
992 priv = GET_PRIV (window);
994 g_return_if_fail (priv->current_chat != NULL);
996 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
997 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
998 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
999 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1003 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1004 if (gtk_text_buffer_get_has_selection (buffer)) {
1005 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1006 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1010 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1012 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1013 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1016 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1017 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1018 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1022 chat_window_cut_activate_cb (GtkAction *action,
1023 EmpathyChatWindow *window)
1025 EmpathyChatWindowPriv *priv;
1027 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1029 priv = GET_PRIV (window);
1031 empathy_chat_cut (priv->current_chat);
1035 chat_window_copy_activate_cb (GtkAction *action,
1036 EmpathyChatWindow *window)
1038 EmpathyChatWindowPriv *priv;
1040 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1042 priv = GET_PRIV (window);
1044 empathy_chat_copy (priv->current_chat);
1048 chat_window_paste_activate_cb (GtkAction *action,
1049 EmpathyChatWindow *window)
1051 EmpathyChatWindowPriv *priv;
1053 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1055 priv = GET_PRIV (window);
1057 empathy_chat_paste (priv->current_chat);
1061 chat_window_find_activate_cb (GtkAction *action,
1062 EmpathyChatWindow *window)
1064 EmpathyChatWindowPriv *priv;
1066 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1068 priv = GET_PRIV (window);
1070 empathy_chat_find (priv->current_chat);
1074 chat_window_tabs_next_activate_cb (GtkAction *action,
1075 EmpathyChatWindow *window)
1077 EmpathyChatWindowPriv *priv;
1079 gint index_, numPages;
1080 gboolean wrap_around;
1082 priv = GET_PRIV (window);
1084 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1085 &wrap_around, NULL);
1087 chat = priv->current_chat;
1088 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1089 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1091 if (index_ == (numPages - 1) && wrap_around) {
1092 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1096 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1100 chat_window_tabs_previous_activate_cb (GtkAction *action,
1101 EmpathyChatWindow *window)
1103 EmpathyChatWindowPriv *priv;
1105 gint index_, numPages;
1106 gboolean wrap_around;
1108 priv = GET_PRIV (window);
1110 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1111 &wrap_around, NULL);
1113 chat = priv->current_chat;
1114 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1115 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1117 if (index_ <= 0 && wrap_around) {
1118 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1122 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1126 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1127 EmpathyChatWindow *window)
1129 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1130 empathy_chat_manager_undo_closed_chat (priv->chat_manager);
1134 chat_window_tabs_left_activate_cb (GtkAction *action,
1135 EmpathyChatWindow *window)
1137 EmpathyChatWindowPriv *priv;
1139 gint index_, num_pages;
1141 priv = GET_PRIV (window);
1143 chat = priv->current_chat;
1144 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1149 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1153 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1154 chat_window_menu_context_update (priv, num_pages);
1158 chat_window_tabs_right_activate_cb (GtkAction *action,
1159 EmpathyChatWindow *window)
1161 EmpathyChatWindowPriv *priv;
1163 gint index_, num_pages;
1165 priv = GET_PRIV (window);
1167 chat = priv->current_chat;
1168 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1170 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1174 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1175 chat_window_menu_context_update (priv, num_pages);
1178 static EmpathyChatWindow *
1179 empathy_chat_window_new (void)
1181 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1185 chat_window_detach_activate_cb (GtkAction *action,
1186 EmpathyChatWindow *window)
1188 EmpathyChatWindowPriv *priv;
1189 EmpathyChatWindow *new_window;
1192 priv = GET_PRIV (window);
1194 chat = priv->current_chat;
1195 new_window = empathy_chat_window_new ();
1197 empathy_chat_window_move_chat (window, new_window, chat);
1199 priv = GET_PRIV (new_window);
1200 gtk_widget_show (priv->dialog);
1204 chat_window_help_contents_activate_cb (GtkAction *action,
1205 EmpathyChatWindow *window)
1207 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1209 empathy_url_show (priv->dialog, "ghelp:empathy");
1213 chat_window_help_about_activate_cb (GtkAction *action,
1214 EmpathyChatWindow *window)
1216 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1218 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1222 chat_window_delete_event_cb (GtkWidget *dialog,
1224 EmpathyChatWindow *window)
1226 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1228 DEBUG ("Delete event received");
1230 g_object_ref (window);
1231 while (priv->chats) {
1232 empathy_chat_window_remove_chat (window, priv->chats->data);
1234 g_object_unref (window);
1240 chat_window_composing_cb (EmpathyChat *chat,
1241 gboolean is_composing,
1242 EmpathyChatWindow *window)
1244 chat_window_update_chat_tab (chat);
1248 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1251 EmpathyChatWindowPriv *priv;
1253 priv = GET_PRIV (window);
1255 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1259 chat_window_notification_closed_cb (NotifyNotification *notify,
1260 EmpathyChatWindow *self)
1262 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1264 g_object_unref (notify);
1265 if (priv->notification == notify) {
1266 priv->notification = NULL;
1271 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1272 EmpathyMessage *message,
1275 EmpathyContact *sender;
1276 const gchar *header;
1280 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1281 gboolean res, has_x_canonical_append;
1282 NotifyNotification *notification = priv->notification;
1284 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1287 res = g_settings_get_boolean (priv->gsettings_notif,
1288 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1295 sender = empathy_message_get_sender (message);
1296 header = empathy_contact_get_alias (sender);
1297 body = empathy_message_get_body (message);
1298 escaped = g_markup_escape_text (body, -1);
1299 has_x_canonical_append = empathy_notify_manager_has_capability (
1300 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1302 if (notification != NULL && !has_x_canonical_append) {
1303 /* if the notification server supports x-canonical-append, it is
1304 better to not use notify_notification_update to avoid
1305 overwriting the current notification message */
1306 notify_notification_update (notification,
1307 header, escaped, NULL);
1309 /* if the notification server supports x-canonical-append,
1310 the hint will be added, so that the message from the
1311 just created notification will be automatically appended
1312 to an existing notification with the same title.
1313 In this way the previous message will not be lost: the new
1314 message will appear below it, in the same notification */
1315 notification = notify_notification_new (header, escaped, NULL);
1317 if (priv->notification == NULL) {
1318 priv->notification = notification;
1321 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1323 tp_g_signal_connect_object (notification, "closed",
1324 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1326 if (has_x_canonical_append) {
1327 notify_notification_set_hint_string (notification,
1328 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
1332 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1333 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1335 if (pixbuf != NULL) {
1336 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1337 g_object_unref (pixbuf);
1340 notify_notification_show (notification, NULL);
1346 chat_window_set_highlight_room_labels (EmpathyChat *chat)
1351 if (!empathy_chat_is_room (chat))
1354 markup = g_markup_printf_escaped (
1355 "<span color=\"red\" weight=\"bold\">%s</span>",
1356 empathy_chat_get_name (chat));
1358 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1359 gtk_label_set_markup (GTK_LABEL (widget), markup);
1361 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1362 gtk_label_set_markup (GTK_LABEL (widget), markup);
1368 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1370 EmpathyChatWindowPriv *priv;
1373 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1375 priv = GET_PRIV (window);
1377 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1383 chat_window_new_message_cb (EmpathyChat *chat,
1384 EmpathyMessage *message,
1386 EmpathyChatWindow *window)
1388 EmpathyChatWindowPriv *priv;
1390 gboolean needs_urgency;
1391 EmpathyContact *sender;
1393 priv = GET_PRIV (window);
1395 has_focus = empathy_chat_window_has_focus (window);
1397 /* - if we're the sender, we play the sound if it's specified in the
1398 * preferences and we're not away.
1399 * - if we receive a message, we play the sound if it's specified in the
1400 * preferences and the window does not have focus on the chat receiving
1404 sender = empathy_message_get_sender (message);
1406 if (empathy_contact_is_user (sender)) {
1407 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1408 EMPATHY_SOUND_MESSAGE_OUTGOING);
1411 if (has_focus && priv->current_chat == chat) {
1412 /* window and tab are focused so consider the message to be read */
1414 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1415 empathy_chat_messages_read (chat);
1419 /* Update the chat tab if this is the first unread message */
1420 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1421 chat_window_update_chat_tab (chat);
1424 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1425 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1426 * an unamed MUC (msn-like).
1427 * In case of a MUC, we set urgency if either:
1428 * a) the chatroom's always_urgent property is TRUE
1429 * b) the message contains our alias
1431 if (empathy_chat_is_room (chat) ||
1432 empathy_chat_get_remote_contact (chat) == NULL) {
1435 EmpathyChatroom *chatroom;
1437 account = empathy_chat_get_account (chat);
1438 room = empathy_chat_get_id (chat);
1440 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1443 if (empathy_chatroom_is_always_urgent (chatroom)) {
1444 needs_urgency = TRUE;
1446 needs_urgency = empathy_message_should_highlight (message);
1449 needs_urgency = TRUE;
1452 if (needs_urgency) {
1454 chat_window_set_urgency_hint (window, TRUE);
1455 chat_window_set_highlight_room_labels (chat);
1458 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1459 EMPATHY_SOUND_MESSAGE_INCOMING);
1461 /* Pending messages have already been displayed in the approver, so we don't
1462 * display a notification for those. */
1464 chat_window_show_or_update_notification (window, message, chat);
1467 /* update the number of unread messages and the window icon */
1468 chat_window_title_update (priv);
1469 chat_window_icon_update (priv, TRUE);
1472 static GtkNotebook *
1473 notebook_create_window_cb (GtkNotebook *source,
1479 EmpathyChatWindowPriv *priv;
1480 EmpathyChatWindow *window, *new_window;
1483 chat = EMPATHY_CHAT (page);
1484 window = chat_window_find_chat (chat);
1486 new_window = empathy_chat_window_new ();
1487 priv = GET_PRIV (new_window);
1489 DEBUG ("Detach hook called");
1491 empathy_chat_window_move_chat (window, new_window, chat);
1493 gtk_widget_show (priv->dialog);
1494 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1500 chat_window_page_switched_cb (GtkNotebook *notebook,
1501 gpointer ignore, /* see note below */
1503 EmpathyChatWindow *window)
1505 EmpathyChatWindowPriv *priv;
1509 DEBUG ("Page switched");
1511 priv = GET_PRIV (window);
1513 /* N.B. in GTK+ 3 child is passed as the first argument to the signal,
1514 * but we can't use that while trying to retain GTK+ 2.x compatibility.
1516 child = gtk_notebook_get_nth_page (notebook, page_num);
1517 chat = EMPATHY_CHAT (child);
1519 if (priv->page_added) {
1520 priv->page_added = FALSE;
1521 empathy_chat_scroll_down (chat);
1523 else if (priv->current_chat == chat) {
1527 priv->current_chat = 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 empathy_chat_messages_read (chat);
1621 if (priv->chats == NULL) {
1622 g_object_unref (window);
1624 chat_window_update (window);
1629 chat_window_focus_in_event_cb (GtkWidget *widget,
1631 EmpathyChatWindow *window)
1633 EmpathyChatWindowPriv *priv;
1635 priv = GET_PRIV (window);
1637 empathy_chat_messages_read (priv->current_chat);
1639 chat_window_set_urgency_hint (window, FALSE);
1641 /* Update the title, since we now mark all unread messages as read. */
1642 chat_window_update_chat_tab (priv->current_chat);
1648 chat_window_drag_drop (GtkWidget *widget,
1649 GdkDragContext *context,
1653 EmpathyChatWindow *window)
1656 EmpathyChatWindowPriv *priv;
1658 priv = GET_PRIV (window);
1660 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1661 if (target == GDK_NONE)
1662 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1664 if (target != GDK_NONE) {
1665 gtk_drag_get_data (widget, context, target, time_);
1673 chat_window_drag_motion (GtkWidget *widget,
1674 GdkDragContext *context,
1678 EmpathyChatWindow *window)
1681 EmpathyChatWindowPriv *priv;
1683 priv = GET_PRIV (window);
1685 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1686 if (target != GDK_NONE) {
1687 /* This is a file drag. Ensure the contact is online and set the
1688 drag type to COPY. Note that it's possible that the tab will
1689 be switched by GTK+ after a timeout from drag_motion without
1690 getting another drag_motion to disable the drop. You have
1691 to hold your mouse really still.
1693 EmpathyContact *contact;
1695 priv = GET_PRIV (window);
1696 contact = empathy_chat_get_remote_contact (priv->current_chat);
1697 /* contact is NULL for multi-user chats. We don't do
1698 * file transfers to MUCs. We also don't send files
1699 * to offline contacts or contacts that don't support
1702 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1703 gdk_drag_status (context, 0, time_);
1706 if (!(empathy_contact_get_capabilities (contact)
1707 & EMPATHY_CAPABILITIES_FT)) {
1708 gdk_drag_status (context, 0, time_);
1711 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1715 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1716 if (target != GDK_NONE) {
1717 /* This is a drag of a contact from a contact list. Set to COPY.
1718 FIXME: If this drag is to a MUC window, it invites the user.
1719 Otherwise, it opens a chat. Should we use a different drag
1720 type for invites? Should we allow ASK?
1722 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1730 chat_window_drag_data_received (GtkWidget *widget,
1731 GdkDragContext *context,
1734 GtkSelectionData *selection,
1737 EmpathyChatWindow *window)
1739 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1740 EmpathyChat *chat = NULL;
1741 EmpathyChatWindow *old_window;
1742 TpAccount *account = NULL;
1743 TpAccountManager *account_manager;
1746 const gchar *account_id;
1747 const gchar *contact_id;
1749 id = (const gchar*) gtk_selection_data_get_data (selection);
1751 /* FIXME: Perhaps should be sure that the account manager is
1752 * prepared before calling _ensure_account on it. */
1753 account_manager = tp_account_manager_dup ();
1755 DEBUG ("DND contact from roster with id:'%s'", id);
1757 strv = g_strsplit (id, ":", 2);
1758 if (g_strv_length (strv) == 2) {
1759 account_id = strv[0];
1760 contact_id = strv[1];
1762 tp_account_manager_ensure_account (account_manager, account_id);
1763 if (account != NULL)
1764 chat = empathy_chat_window_find_chat (account, contact_id);
1767 if (account == NULL) {
1769 gtk_drag_finish (context, FALSE, FALSE, time_);
1774 empathy_dispatcher_chat_with_contact_id (
1775 account, contact_id, gtk_get_current_event_time ());
1780 g_object_unref (account_manager);
1783 old_window = chat_window_find_chat (chat);
1785 if (old_window == window) {
1786 gtk_drag_finish (context, TRUE, FALSE, time_);
1790 empathy_chat_window_move_chat (old_window, window, chat);
1792 empathy_chat_window_add_chat (window, chat);
1795 /* Added to take care of any outstanding chat events */
1796 empathy_chat_window_present_chat (chat,
1797 TP_USER_ACTION_TIME_NOT_USER_ACTION);
1799 /* We should return TRUE to remove the data when doing
1800 * GDK_ACTION_MOVE, but we don't here otherwise it has
1801 * weird consequences, and we handle that internally
1802 * anyway with add_chat () and remove_chat ().
1804 gtk_drag_finish (context, TRUE, FALSE, time_);
1806 else if (info == DND_DRAG_TYPE_URI_LIST) {
1807 EmpathyChatWindowPriv *priv;
1808 EmpathyContact *contact;
1811 priv = GET_PRIV (window);
1812 contact = empathy_chat_get_remote_contact (priv->current_chat);
1814 /* contact is NULL when current_chat is a multi-user chat.
1815 * We don't do file transfers to MUCs, so just cancel the drag.
1817 if (contact == NULL) {
1818 gtk_drag_finish (context, TRUE, FALSE, time_);
1822 data = (const gchar *) gtk_selection_data_get_data (selection);
1823 empathy_send_file_from_uri_list (contact, data);
1825 gtk_drag_finish (context, TRUE, FALSE, time_);
1827 else if (info == DND_DRAG_TYPE_TAB) {
1829 EmpathyChatWindow *old_window = NULL;
1833 chat = (void *) gtk_selection_data_get_data (selection);
1834 old_window = chat_window_find_chat (*chat);
1837 EmpathyChatWindowPriv *priv;
1839 priv = GET_PRIV (window);
1840 priv->dnd_same_window = (old_window == window);
1841 DEBUG ("DND tab (within same window: %s)",
1842 priv->dnd_same_window ? "Yes" : "No");
1845 DEBUG ("DND from unknown source");
1846 gtk_drag_finish (context, FALSE, FALSE, time_);
1851 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1852 guint num_chats_in_manager,
1853 EmpathyChatWindow *window)
1855 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1857 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1858 num_chats_in_manager > 0);
1862 chat_window_finalize (GObject *object)
1864 EmpathyChatWindow *window;
1865 EmpathyChatWindowPriv *priv;
1867 window = EMPATHY_CHAT_WINDOW (object);
1868 priv = GET_PRIV (window);
1870 DEBUG ("Finalized: %p", object);
1872 g_object_unref (priv->ui_manager);
1873 g_object_unref (priv->chatroom_manager);
1874 g_object_unref (priv->notify_mgr);
1875 g_object_unref (priv->gsettings_chat);
1876 g_object_unref (priv->gsettings_notif);
1877 g_object_unref (priv->gsettings_ui);
1878 g_object_unref (priv->sound_mgr);
1880 if (priv->notification != NULL) {
1881 notify_notification_close (priv->notification, NULL);
1882 priv->notification = NULL;
1885 if (priv->contact_targets) {
1886 gtk_target_list_unref (priv->contact_targets);
1888 if (priv->file_targets) {
1889 gtk_target_list_unref (priv->file_targets);
1892 if (priv->chat_manager) {
1893 g_signal_handler_disconnect (priv->chat_manager,
1894 priv->chat_manager_chats_changed_id);
1895 g_object_unref (priv->chat_manager);
1896 priv->chat_manager = NULL;
1899 chat_windows = g_list_remove (chat_windows, window);
1900 gtk_widget_destroy (priv->dialog);
1902 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1906 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1908 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1910 object_class->finalize = chat_window_finalize;
1912 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1914 /* Set up a style for the close button with no focus padding. */
1915 gtk_rc_parse_string (
1916 "style \"empathy-close-button-style\"\n"
1918 " GtkWidget::focus-padding = 0\n"
1922 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1926 empathy_chat_window_init (EmpathyChatWindow *window)
1929 GtkAccelGroup *accel_group;
1934 GtkWidget *chat_vbox;
1936 EmpathySmileyManager *smiley_manager;
1937 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1938 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1940 window->priv = priv;
1941 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1942 gui = empathy_builder_get_file (filename,
1943 "chat_window", &priv->dialog,
1944 "chat_vbox", &chat_vbox,
1945 "ui_manager", &priv->ui_manager,
1946 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1947 "menu_conv_favorite", &priv->menu_conv_favorite,
1948 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
1949 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1950 "menu_edit_cut", &priv->menu_edit_cut,
1951 "menu_edit_copy", &priv->menu_edit_copy,
1952 "menu_edit_paste", &priv->menu_edit_paste,
1953 "menu_edit_find", &priv->menu_edit_find,
1954 "menu_tabs_next", &priv->menu_tabs_next,
1955 "menu_tabs_prev", &priv->menu_tabs_prev,
1956 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1957 "menu_tabs_left", &priv->menu_tabs_left,
1958 "menu_tabs_right", &priv->menu_tabs_right,
1959 "menu_tabs_detach", &priv->menu_tabs_detach,
1963 empathy_builder_connect (gui, window,
1964 "menu_conv", "activate", chat_window_conv_activate_cb,
1965 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1966 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1967 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
1968 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1969 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1970 "menu_conv_close", "activate", chat_window_close_activate_cb,
1971 "menu_edit", "activate", chat_window_edit_activate_cb,
1972 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1973 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1974 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1975 "menu_edit_find", "activate", chat_window_find_activate_cb,
1976 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1977 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1978 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1979 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1980 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1981 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1982 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1983 "menu_help_about", "activate", chat_window_help_about_activate_cb,
1986 g_object_ref (priv->ui_manager);
1987 g_object_unref (gui);
1989 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1990 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
1991 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
1992 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1994 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1996 priv->notebook = gtk_notebook_new ();
1998 g_signal_connect (priv->notebook, "create-window",
1999 G_CALLBACK (notebook_create_window_cb), window);
2001 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2002 "EmpathyChatWindow");
2003 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2004 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2005 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2006 gtk_widget_show (priv->notebook);
2009 accel_group = gtk_accel_group_new ();
2010 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2012 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2013 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2016 gtk_accel_group_connect (accel_group,
2023 g_object_unref (accel_group);
2025 /* Set up drag target lists */
2026 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2027 G_N_ELEMENTS (drag_types_dest_contact));
2028 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2029 G_N_ELEMENTS (drag_types_dest_file));
2031 /* Set up smiley menu */
2032 smiley_manager = empathy_smiley_manager_dup_singleton ();
2033 submenu = empathy_smiley_menu_new (smiley_manager,
2034 chat_window_insert_smiley_activate_cb,
2036 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2037 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2038 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2039 g_object_unref (smiley_manager);
2041 /* Set up signals we can't do with ui file since we may need to
2042 * block/unblock them at some later stage.
2045 g_signal_connect (priv->dialog,
2047 G_CALLBACK (chat_window_delete_event_cb),
2049 g_signal_connect (priv->dialog,
2051 G_CALLBACK (chat_window_focus_in_event_cb),
2053 g_signal_connect_after (priv->notebook,
2055 G_CALLBACK (chat_window_page_switched_cb),
2057 g_signal_connect (priv->notebook,
2059 G_CALLBACK (chat_window_page_added_cb),
2061 g_signal_connect (priv->notebook,
2063 G_CALLBACK (chat_window_page_removed_cb),
2066 /* Set up drag and drop */
2067 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2068 GTK_DEST_DEFAULT_HIGHLIGHT,
2070 G_N_ELEMENTS (drag_types_dest),
2071 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2073 /* connect_after to allow GtkNotebook's built-in tab switching */
2074 g_signal_connect_after (priv->notebook,
2076 G_CALLBACK (chat_window_drag_motion),
2078 g_signal_connect (priv->notebook,
2079 "drag-data-received",
2080 G_CALLBACK (chat_window_drag_data_received),
2082 g_signal_connect (priv->notebook,
2084 G_CALLBACK (chat_window_drag_drop),
2087 chat_windows = g_list_prepend (chat_windows, window);
2089 /* Set up private details */
2091 priv->current_chat = NULL;
2092 priv->notification = NULL;
2094 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2096 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2097 priv->chat_manager_chats_changed_id =
2098 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2099 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2102 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2103 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2108 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2110 EmpathyChatWindowPriv *priv;
2112 g_return_val_if_fail (window != NULL, NULL);
2114 priv = GET_PRIV (window);
2116 return priv->dialog;
2119 /* Returns the window to open a new tab in if there is a suitable window,
2120 * otherwise, returns NULL indicating that a new window should be added.
2122 static EmpathyChatWindow *
2123 empathy_chat_window_get_default (gboolean room)
2125 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2127 gboolean separate_windows = TRUE;
2129 separate_windows = g_settings_get_boolean (gsettings,
2130 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2132 g_object_unref (gsettings);
2134 if (separate_windows) {
2135 /* Always create a new window */
2139 for (l = chat_windows; l; l = l->next) {
2140 EmpathyChatWindowPriv *priv;
2141 EmpathyChatWindow *chat_window;
2143 guint nb_rooms, nb_private;
2145 chat_window = l->data;
2146 priv = GET_PRIV (chat_window);
2148 dialog = empathy_chat_window_get_dialog (chat_window);
2150 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2152 /* Skip the window if there aren't any rooms in it */
2153 if (room && nb_rooms == 0)
2156 /* Skip the window if there aren't any 1-1 chats in it */
2157 if (!room && nb_private == 0)
2160 /* Found a window on this desktop, make it visible if necessary */
2161 if (!empathy_window_get_is_visible (GTK_WINDOW (dialog)))
2162 empathy_window_present (GTK_WINDOW (dialog));
2170 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2173 EmpathyChatWindowPriv *priv;
2175 GtkWidget *popup_label;
2177 GValue value = { 0, };
2179 g_return_if_fail (window != NULL);
2180 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2182 priv = GET_PRIV (window);
2184 /* Reference the chat object */
2185 g_object_ref (chat);
2187 /* If this window has just been created, position it */
2188 if (priv->chats == NULL) {
2189 const gchar *name = "chat-window";
2190 gboolean separate_windows;
2192 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2193 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2195 if (empathy_chat_is_room (chat))
2196 name = "room-window";
2198 if (separate_windows) {
2201 /* Save current position of the window */
2202 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2204 /* First bind to the 'generic' name. So new window for which we didn't
2205 * save a geometry yet will have the geometry of the last saved
2206 * window (bgo #601191). */
2207 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2209 /* Restore previous position of the window so the newly created window
2210 * won't be in the same position as the latest saved window and so
2211 * completely hide it. */
2212 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2214 /* Then bind it to the name of the contact/room so we'll save the
2215 * geometry specific to this window */
2216 name = empathy_chat_get_id (chat);
2219 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2222 child = GTK_WIDGET (chat);
2223 label = chat_window_create_label (window, chat, TRUE);
2224 popup_label = chat_window_create_label (window, chat, FALSE);
2225 gtk_widget_show (child);
2227 g_signal_connect (chat, "notify::name",
2228 G_CALLBACK (chat_window_chat_notify_cb),
2230 g_signal_connect (chat, "notify::subject",
2231 G_CALLBACK (chat_window_chat_notify_cb),
2233 g_signal_connect (chat, "notify::remote-contact",
2234 G_CALLBACK (chat_window_chat_notify_cb),
2236 chat_window_chat_notify_cb (chat);
2238 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2239 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2240 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2241 g_value_init (&value, G_TYPE_BOOLEAN);
2242 g_value_set_boolean (&value, TRUE);
2243 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2244 child, "tab-expand" , &value);
2245 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2246 child, "tab-fill" , &value);
2247 g_value_unset (&value);
2249 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2253 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2256 EmpathyChatWindowPriv *priv;
2258 EmpathyContact *remote_contact;
2259 EmpathyChatManager *chat_manager;
2261 g_return_if_fail (window != NULL);
2262 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2264 priv = GET_PRIV (window);
2266 g_signal_handlers_disconnect_by_func (chat,
2267 chat_window_chat_notify_cb,
2269 remote_contact = g_object_get_data (G_OBJECT (chat),
2270 "chat-window-remote-contact");
2271 if (remote_contact) {
2272 g_signal_handlers_disconnect_by_func (remote_contact,
2273 chat_window_update_chat_tab,
2277 chat_manager = empathy_chat_manager_dup_singleton ();
2278 empathy_chat_manager_closed_chat (chat_manager, chat);
2279 g_object_unref (chat_manager);
2281 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2283 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2285 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2287 g_object_unref (chat);
2291 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2292 EmpathyChatWindow *new_window,
2297 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2298 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2299 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2301 widget = GTK_WIDGET (chat);
2303 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2304 G_OBJECT (widget)->ref_count);
2306 /* We reference here to make sure we don't loose the widget
2307 * and the EmpathyChat object during the move.
2309 g_object_ref (chat);
2310 g_object_ref (widget);
2312 empathy_chat_window_remove_chat (old_window, chat);
2313 empathy_chat_window_add_chat (new_window, chat);
2315 g_object_unref (widget);
2316 g_object_unref (chat);
2320 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2323 EmpathyChatWindowPriv *priv;
2326 g_return_if_fail (window != NULL);
2327 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2329 priv = GET_PRIV (window);
2331 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2333 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2338 empathy_chat_window_find_chat (TpAccount *account,
2343 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2345 for (l = chat_windows; l; l = l->next) {
2346 EmpathyChatWindowPriv *priv;
2347 EmpathyChatWindow *window;
2351 priv = GET_PRIV (window);
2353 for (ll = priv->chats; ll; ll = ll->next) {
2358 if (account == empathy_chat_get_account (chat) &&
2359 !tp_strdiff (id, empathy_chat_get_id (chat))) {
2369 empathy_chat_window_present_chat (EmpathyChat *chat,
2372 EmpathyChatWindow *window;
2373 EmpathyChatWindowPriv *priv;
2374 guint32 x_timestamp;
2376 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2378 window = chat_window_find_chat (chat);
2380 /* If the chat has no window, create one */
2381 if (window == NULL) {
2382 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2384 window = empathy_chat_window_new ();
2385 gtk_widget_show_all (GET_PRIV (window)->dialog);
2388 empathy_chat_window_add_chat (window, chat);
2391 /* Don't force the window to show itself when it wasn't
2392 * an action by the user
2394 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2397 priv = GET_PRIV (window);
2399 if (x_timestamp != GDK_CURRENT_TIME) {
2400 /* Don't present or switch tab if the action was earlier than the
2401 * last actions X time, accounting for overflow and the first ever
2404 if (priv->x_user_action_time != 0
2405 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2408 priv->x_user_action_time = x_timestamp;
2411 empathy_chat_window_switch_to_chat (window, chat);
2412 empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2415 gtk_widget_grab_focus (chat->input_text_view);
2419 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2423 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2425 guint _nb_rooms = 0, _nb_private = 0;
2427 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2428 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2434 if (nb_rooms != NULL)
2435 *nb_rooms = _nb_rooms;
2436 if (nb_private != NULL)
2437 *nb_private = _nb_private;