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-client-factory.h>
42 #include <libempathy/empathy-contact.h>
43 #include <libempathy/empathy-message.h>
44 #include <libempathy/empathy-chatroom-manager.h>
45 #include <libempathy/empathy-gsettings.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-tp-contact-factory.h>
48 #include <libempathy/empathy-contact-list.h>
49 #include <libempathy/empathy-request-util.h>
51 #include <libempathy-gtk/empathy-images.h>
52 #include <libempathy-gtk/empathy-contact-dialogs.h>
53 #include <libempathy-gtk/empathy-log-window.h>
54 #include <libempathy-gtk/empathy-geometry.h>
55 #include <libempathy-gtk/empathy-smiley-manager.h>
56 #include <libempathy-gtk/empathy-sound-manager.h>
57 #include <libempathy-gtk/empathy-ui-utils.h>
58 #include <libempathy-gtk/empathy-notify-manager.h>
60 #include "empathy-chat-manager.h"
61 #include "empathy-chat-window.h"
62 #include "empathy-about-dialog.h"
63 #include "empathy-invite-participant-dialog.h"
64 #include "gedit-close-button.h"
66 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
67 #include <libempathy/empathy-debug.h>
69 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
71 #define X_EARLIER_OR_EQL(t1, t2) \
72 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
73 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
76 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
78 EmpathyChat *current_chat;
81 gboolean dnd_same_window;
82 EmpathyChatroomManager *chatroom_manager;
83 EmpathyNotifyManager *notify_mgr;
86 NotifyNotification *notification;
88 GtkTargetList *contact_targets;
89 GtkTargetList *file_targets;
91 EmpathyChatManager *chat_manager;
92 gulong chat_manager_chats_changed_id;
95 GtkUIManager *ui_manager;
96 GtkAction *menu_conv_insert_smiley;
97 GtkAction *menu_conv_favorite;
98 GtkAction *menu_conv_always_urgent;
99 GtkAction *menu_conv_toggle_contacts;
101 GtkAction *menu_edit_cut;
102 GtkAction *menu_edit_copy;
103 GtkAction *menu_edit_paste;
104 GtkAction *menu_edit_find;
106 GtkAction *menu_tabs_next;
107 GtkAction *menu_tabs_prev;
108 GtkAction *menu_tabs_undo_close_tab;
109 GtkAction *menu_tabs_left;
110 GtkAction *menu_tabs_right;
111 GtkAction *menu_tabs_detach;
113 /* Last user action time we acted upon to show a tab */
114 guint32 x_user_action_time;
116 GSettings *gsettings_chat;
117 GSettings *gsettings_notif;
118 GSettings *gsettings_ui;
120 EmpathySoundManager *sound_mgr;
121 } EmpathyChatWindowPriv;
123 static GList *chat_windows = NULL;
125 static const guint tab_accel_keys[] = {
126 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
127 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
131 DND_DRAG_TYPE_CONTACT_ID,
132 DND_DRAG_TYPE_URI_LIST,
136 static const GtkTargetEntry drag_types_dest[] = {
137 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
138 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
139 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
140 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] = {
144 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
147 static const GtkTargetEntry drag_types_dest_file[] = {
148 /* must be first to be prioritized, in order to receive the
149 * note's file path from Tomboy instead of an URI */
150 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
151 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
154 static void chat_window_update (EmpathyChatWindow *window,
155 gboolean update_contact_menu);
157 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
160 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
163 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
164 EmpathyChatWindow *new_window,
167 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
171 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
174 chat_window_accel_cb (GtkAccelGroup *accelgroup,
178 EmpathyChatWindow *window)
180 EmpathyChatWindowPriv *priv;
184 priv = GET_PRIV (window);
186 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
187 if (tab_accel_keys[i] == key) {
194 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
198 static EmpathyChatWindow *
199 chat_window_find_chat (EmpathyChat *chat)
201 EmpathyChatWindowPriv *priv;
204 for (l = chat_windows; l; l = l->next) {
205 priv = GET_PRIV (l->data);
206 ll = g_list_find (priv->chats, chat);
216 confirm_close_response_cb (GtkWidget *dialog,
218 EmpathyChatWindow *window)
222 chat = g_object_get_data (G_OBJECT (dialog), "chat");
224 gtk_widget_destroy (dialog);
226 if (response == GTK_RESPONSE_ACCEPT)
227 empathy_chat_window_remove_chat (window, chat);
231 maybe_close_chat (EmpathyChatWindow *window,
234 EmpathyChatWindowPriv *priv;
236 priv = GET_PRIV (window);
238 if (empathy_chat_is_room (chat)) {
239 gchar *chat_name = empathy_chat_dup_name (chat);
240 GtkWidget *dialog = gtk_message_dialog_new (
241 GTK_WINDOW (priv->dialog),
242 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
248 gtk_window_set_title (GTK_WINDOW (dialog), "");
250 gtk_message_dialog_format_secondary_text (
251 GTK_MESSAGE_DIALOG (dialog),
252 _("You will not receive any further messages from "
253 "this chat room unless you rejoin."));
255 gtk_dialog_add_button (GTK_DIALOG (dialog),
256 _("Leave chat room"), GTK_RESPONSE_ACCEPT);
257 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
258 GTK_RESPONSE_ACCEPT);
260 g_object_set_data (G_OBJECT (dialog), "chat", chat);
262 g_signal_connect (dialog, "response",
263 G_CALLBACK (confirm_close_response_cb), window);
265 gtk_window_present (GTK_WINDOW (dialog));
268 empathy_chat_window_remove_chat (window, chat);
273 chat_window_close_clicked_cb (GtkAction *action,
276 EmpathyChatWindow *window;
278 window = chat_window_find_chat (chat);
279 maybe_close_chat (window, chat);
283 chat_tab_style_updated_cb (GtkWidget *hbox,
287 int char_width, h, w;
288 PangoContext *context;
289 const PangoFontDescription *font_desc;
290 PangoFontMetrics *metrics;
292 button = g_object_get_data (G_OBJECT (user_data),
293 "chat-window-tab-close-button");
294 context = gtk_widget_get_pango_context (hbox);
296 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
297 GTK_STATE_FLAG_NORMAL);
299 metrics = pango_context_get_metrics (context, font_desc,
300 pango_context_get_language (context));
301 char_width = pango_font_metrics_get_approximate_char_width (metrics);
302 pango_font_metrics_unref (metrics);
304 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
305 GTK_ICON_SIZE_MENU, &w, &h);
307 /* Request at least about 12 chars width plus at least space for the status
308 * image and the close button */
309 gtk_widget_set_size_request (hbox,
310 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
312 gtk_widget_set_size_request (button, w, h);
316 chat_window_create_label (EmpathyChatWindow *window,
318 gboolean is_tab_label)
321 GtkWidget *name_label;
322 GtkWidget *status_image;
323 GtkWidget *event_box;
324 GtkWidget *event_box_hbox;
325 PangoAttrList *attr_list;
326 PangoAttribute *attr;
328 /* The spacing between the button and the label. */
329 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
331 event_box = gtk_event_box_new ();
332 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
334 name_label = gtk_label_new (NULL);
336 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
338 attr_list = pango_attr_list_new ();
339 attr = pango_attr_scale_new (1/1.2);
340 attr->start_index = 0;
341 attr->end_index = -1;
342 pango_attr_list_insert (attr_list, attr);
343 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
344 pango_attr_list_unref (attr_list);
346 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
347 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
348 g_object_set_data (G_OBJECT (chat),
349 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
352 status_image = gtk_image_new ();
354 /* Spacing between the icon and label. */
355 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
357 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
358 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
360 g_object_set_data (G_OBJECT (chat),
361 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
363 g_object_set_data (G_OBJECT (chat),
364 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
367 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
368 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
371 GtkWidget *close_button;
372 GtkWidget *sending_spinner;
374 sending_spinner = gtk_spinner_new ();
376 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
378 g_object_set_data (G_OBJECT (chat),
379 "chat-window-tab-sending-spinner",
382 close_button = gedit_close_button_new ();
383 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
385 /* We don't want focus/keynav for the button to avoid clutter, and
386 * Ctrl-W works anyway.
388 gtk_widget_set_can_focus (close_button, FALSE);
389 gtk_widget_set_can_default (close_button, FALSE);
391 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
393 g_signal_connect (close_button,
395 G_CALLBACK (chat_window_close_clicked_cb),
398 /* React to theme changes and also setup the size correctly. */
399 g_signal_connect (hbox,
401 G_CALLBACK (chat_tab_style_updated_cb),
405 gtk_widget_show_all (hbox);
411 _submenu_notify_visible_changed_cb (GObject *object,
415 g_signal_handlers_disconnect_by_func (object,
416 _submenu_notify_visible_changed_cb,
418 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
422 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
427 gboolean wrap_around;
428 gboolean is_connected;
431 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
432 first_page = (page_num == 0);
433 last_page = (page_num == (num_pages - 1));
434 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
436 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
438 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
440 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
442 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
443 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
444 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
445 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
449 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
450 EmpathyChatWindow *self)
452 EmpathyTpChat *tp_chat;
453 TpConnection *connection;
455 gboolean sensitive = FALSE;
457 g_return_if_fail (priv->current_chat != NULL);
459 action = gtk_ui_manager_get_action (priv->ui_manager,
460 "/chats_menubar/menu_conv/menu_conv_invite_participant");
461 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
463 if (tp_chat != NULL) {
464 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
466 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
467 (tp_connection_get_status (connection, NULL) ==
468 TP_CONNECTION_STATUS_CONNECTED);
471 gtk_action_set_sensitive (action, sensitive);
475 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
476 EmpathyChatWindow *window)
478 GtkWidget *menu, *submenu, *orig_submenu;
480 menu = gtk_ui_manager_get_widget (priv->ui_manager,
481 "/chats_menubar/menu_contact");
482 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
484 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
485 submenu = empathy_chat_get_contact_menu (priv->current_chat);
487 if (submenu != NULL) {
488 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
489 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
491 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
492 gtk_widget_show (menu);
493 gtk_widget_set_sensitive (menu, TRUE);
495 gtk_widget_set_sensitive (menu, FALSE);
498 tp_g_signal_connect_object (orig_submenu,
500 (GCallback)_submenu_notify_visible_changed_cb,
506 get_all_unread_messages (EmpathyChatWindowPriv *priv)
511 for (l = priv->chats; l != NULL; l = g_list_next (l))
512 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
518 get_window_title_name (EmpathyChatWindowPriv *priv)
520 gchar *active_name, *ret;
522 guint current_unread_msgs;
524 nb_chats = g_list_length (priv->chats);
525 g_assert (nb_chats > 0);
527 active_name = empathy_chat_dup_name (priv->current_chat);
529 current_unread_msgs = empathy_chat_get_nb_unread_messages (
534 if (current_unread_msgs == 0)
535 ret = g_strdup (active_name);
537 ret = g_strdup_printf (ngettext (
539 "%s (%d unread)", current_unread_msgs),
540 active_name, current_unread_msgs);
542 guint nb_others = nb_chats - 1;
543 guint all_unread_msgs;
545 all_unread_msgs = get_all_unread_messages (priv);
547 if (all_unread_msgs == 0) {
548 /* no unread message */
549 ret = g_strdup_printf (ngettext (
551 "%s (and %u others)", nb_others),
552 active_name, nb_others);
555 else if (all_unread_msgs == current_unread_msgs) {
556 /* unread messages are in the current tab */
557 ret = g_strdup_printf (ngettext (
559 "%s (%d unread)", current_unread_msgs),
560 active_name, current_unread_msgs);
563 else if (current_unread_msgs == 0) {
564 /* unread messages are in other tabs */
565 ret = g_strdup_printf (ngettext (
566 "%s (%d unread from others)",
567 "%s (%d unread from others)",
569 active_name, all_unread_msgs);
573 /* unread messages are in all the tabs */
574 ret = g_strdup_printf (ngettext (
575 "%s (%d unread from all)",
576 "%s (%d unread from all)",
578 active_name, all_unread_msgs);
582 g_free (active_name);
588 chat_window_title_update (EmpathyChatWindowPriv *priv)
592 name = get_window_title_name (priv);
593 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
598 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
601 EmpathyContact *remote_contact;
602 gboolean avatar_in_icon;
605 n_chats = g_list_length (priv->chats);
607 /* Update window icon */
609 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
610 EMPATHY_IMAGE_MESSAGE);
612 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
613 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
615 if (n_chats == 1 && avatar_in_icon) {
616 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
617 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
618 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
621 g_object_unref (icon);
624 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
630 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
634 GtkWidget *chat_close_button;
637 if (num_pages == 1) {
638 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
639 chat_close_button = g_object_get_data (G_OBJECT (chat),
640 "chat-window-tab-close-button");
641 gtk_widget_hide (chat_close_button);
643 for (i=0; i<num_pages; i++) {
644 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
645 chat_close_button = g_object_get_data (G_OBJECT (chat),
646 "chat-window-tab-close-button");
647 gtk_widget_show (chat_close_button);
653 chat_window_update (EmpathyChatWindow *window,
654 gboolean update_contact_menu)
656 EmpathyChatWindowPriv *priv = GET_PRIV (window);
659 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
661 /* Update Tab menu */
662 chat_window_menu_context_update (priv,
665 chat_window_conversation_menu_update (priv, window);
667 /* If this update is due to a focus-in event, we know the menu will be
668 the same as when we last left it, so no work to do. Besides, if we
669 swap out the menu on a focus-in, we may confuse any external global
671 if (update_contact_menu) {
672 chat_window_contact_menu_update (priv,
676 chat_window_title_update (priv);
678 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
680 chat_window_close_button_update (priv,
685 append_markup_printf (GString *string,
692 va_start (args, format);
694 tmp = g_markup_vprintf_escaped (format, args);
695 g_string_append (string, tmp);
702 chat_window_update_chat_tab_full (EmpathyChat *chat,
703 gboolean update_contact_menu)
705 EmpathyChatWindow *window;
706 EmpathyChatWindowPriv *priv;
707 EmpathyContact *remote_contact;
711 const gchar *subject;
712 const gchar *status = NULL;
716 const gchar *icon_name;
717 GtkWidget *tab_image;
718 GtkWidget *menu_image;
719 GtkWidget *sending_spinner;
722 window = chat_window_find_chat (chat);
726 priv = GET_PRIV (window);
728 /* Get information */
729 name = empathy_chat_dup_name (chat);
730 account = empathy_chat_get_account (chat);
731 subject = empathy_chat_get_subject (chat);
732 remote_contact = empathy_chat_get_remote_contact (chat);
734 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
735 name, tp_proxy_get_object_path (account), subject, remote_contact);
737 /* Update tab image */
738 if (empathy_chat_get_tp_chat (chat) == NULL) {
739 /* No TpChat, we are disconnected */
742 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
743 icon_name = EMPATHY_IMAGE_MESSAGE;
745 else if (remote_contact && empathy_chat_is_composing (chat)) {
746 icon_name = EMPATHY_IMAGE_TYPING;
748 else if (empathy_chat_is_sms_channel (chat)) {
749 icon_name = EMPATHY_IMAGE_SMS;
751 else if (remote_contact) {
752 icon_name = empathy_icon_name_for_contact (remote_contact);
754 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
757 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
758 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
759 if (icon_name != NULL) {
760 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
761 gtk_widget_show (tab_image);
762 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
763 gtk_widget_show (menu_image);
765 gtk_widget_hide (tab_image);
766 gtk_widget_hide (menu_image);
769 /* Update the sending spinner */
770 nb_sending = empathy_chat_get_n_messages_sending (chat);
771 sending_spinner = g_object_get_data (G_OBJECT (chat),
772 "chat-window-tab-sending-spinner");
774 g_object_set (sending_spinner,
775 "active", nb_sending > 0,
776 "visible", nb_sending > 0,
779 /* Update tab tooltip */
780 tooltip = g_string_new (NULL);
782 if (remote_contact) {
783 id = empathy_contact_get_id (remote_contact);
784 status = empathy_contact_get_presence_message (remote_contact);
789 if (empathy_chat_is_sms_channel (chat)) {
790 append_markup_printf (tooltip, "%s ", _("SMS:"));
793 append_markup_printf (tooltip,
794 "<b>%s</b><small> (%s)</small>",
796 tp_account_get_display_name (account));
798 if (nb_sending > 0) {
799 char *tmp = g_strdup_printf (
800 ngettext ("Sending %d message",
801 "Sending %d messages",
805 g_string_append (tooltip, "\n");
806 g_string_append (tooltip, tmp);
808 gtk_widget_set_tooltip_text (sending_spinner, tmp);
812 if (!EMP_STR_EMPTY (status)) {
813 append_markup_printf (tooltip, "\n<i>%s</i>", status);
817 append_markup_printf (tooltip, "\n<b>%s</b> %s",
818 _("Topic:"), subject);
821 if (remote_contact && empathy_chat_is_composing (chat)) {
822 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
825 markup = g_string_free (tooltip, FALSE);
826 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
827 gtk_widget_set_tooltip_markup (widget, markup);
828 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
829 gtk_widget_set_tooltip_markup (widget, markup);
832 /* Update tab and menu label */
833 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
834 gtk_label_set_text (GTK_LABEL (widget), name);
835 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
836 gtk_label_set_text (GTK_LABEL (widget), name);
838 /* Update the window if it's the current chat */
839 if (priv->current_chat == chat) {
840 chat_window_update (window, update_contact_menu);
847 chat_window_update_chat_tab (EmpathyChat *chat)
849 chat_window_update_chat_tab_full (chat, TRUE);
853 chat_window_chat_notify_cb (EmpathyChat *chat)
855 EmpathyChatWindow *window;
856 EmpathyContact *old_remote_contact;
857 EmpathyContact *remote_contact = NULL;
859 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
860 remote_contact = empathy_chat_get_remote_contact (chat);
862 if (old_remote_contact != remote_contact) {
863 /* The remote-contact associated with the chat changed, we need
864 * to keep track of any change of that contact and update the
865 * window each time. */
866 if (remote_contact) {
867 g_signal_connect_swapped (remote_contact, "notify",
868 G_CALLBACK (chat_window_update_chat_tab),
871 if (old_remote_contact) {
872 g_signal_handlers_disconnect_by_func (old_remote_contact,
873 chat_window_update_chat_tab,
877 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
878 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
881 chat_window_update_chat_tab (chat);
883 window = chat_window_find_chat (chat);
884 if (window != NULL) {
885 chat_window_update (window, FALSE);
890 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
891 EmpathySmiley *smiley,
894 EmpathyChatWindowPriv *priv = GET_PRIV (window);
896 GtkTextBuffer *buffer;
899 chat = priv->current_chat;
901 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
902 gtk_text_buffer_get_end_iter (buffer, &iter);
903 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
907 chat_window_conv_activate_cb (GtkAction *action,
908 EmpathyChatWindow *window)
910 EmpathyChatWindowPriv *priv = GET_PRIV (window);
913 EmpathyContact *remote_contact = NULL;
915 /* Favorite room menu */
916 is_room = empathy_chat_is_room (priv->current_chat);
920 gboolean found = FALSE;
921 EmpathyChatroom *chatroom;
923 room = empathy_chat_get_id (priv->current_chat);
924 account = empathy_chat_get_account (priv->current_chat);
925 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
927 if (chatroom != NULL)
928 found = empathy_chatroom_is_favorite (chatroom);
930 DEBUG ("This room %s favorite", found ? "is" : "is not");
931 gtk_toggle_action_set_active (
932 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
934 if (chatroom != NULL)
935 found = empathy_chatroom_is_always_urgent (chatroom);
937 gtk_toggle_action_set_active (
938 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
941 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
942 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
944 /* Show contacts menu */
945 g_object_get (priv->current_chat,
946 "remote-contact", &remote_contact,
947 "show-contacts", &active,
949 if (remote_contact == NULL) {
950 gtk_toggle_action_set_active (
951 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
954 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
955 (remote_contact == NULL));
956 if (remote_contact != NULL) {
957 g_object_unref (remote_contact);
962 chat_window_clear_activate_cb (GtkAction *action,
963 EmpathyChatWindow *window)
965 EmpathyChatWindowPriv *priv = GET_PRIV (window);
967 empathy_chat_clear (priv->current_chat);
971 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
972 EmpathyChatWindow *window)
974 EmpathyChatWindowPriv *priv = GET_PRIV (window);
979 EmpathyChatroom *chatroom;
981 active = gtk_toggle_action_get_active (toggle_action);
982 account = empathy_chat_get_account (priv->current_chat);
983 room = empathy_chat_get_id (priv->current_chat);
984 name = empathy_chat_dup_name (priv->current_chat);
986 chatroom = empathy_chatroom_manager_ensure_chatroom (
987 priv->chatroom_manager,
992 empathy_chatroom_set_favorite (chatroom, active);
993 g_object_unref (chatroom);
998 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
999 EmpathyChatWindow *window)
1001 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1006 EmpathyChatroom *chatroom;
1008 active = gtk_toggle_action_get_active (toggle_action);
1009 account = empathy_chat_get_account (priv->current_chat);
1010 room = empathy_chat_get_id (priv->current_chat);
1011 name = empathy_chat_dup_name (priv->current_chat);
1013 chatroom = empathy_chatroom_manager_ensure_chatroom (
1014 priv->chatroom_manager,
1019 empathy_chatroom_set_always_urgent (chatroom, active);
1020 g_object_unref (chatroom);
1025 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1026 EmpathyChatWindow *window)
1028 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1031 active = gtk_toggle_action_get_active (toggle_action);
1033 empathy_chat_set_show_contacts (priv->current_chat, active);
1037 chat_window_invite_participant_activate_cb (GtkAction *action,
1038 EmpathyChatWindow *window)
1040 EmpathyChatWindowPriv *priv;
1042 EmpathyTpChat *tp_chat;
1045 priv = GET_PRIV (window);
1047 g_return_if_fail (priv->current_chat != NULL);
1049 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1051 dialog = empathy_invite_participant_dialog_new (
1052 GTK_WINDOW (priv->dialog), tp_chat);
1053 gtk_widget_show (dialog);
1055 response = gtk_dialog_run (GTK_DIALOG (dialog));
1057 if (response == GTK_RESPONSE_ACCEPT) {
1058 TpContact *tp_contact;
1059 EmpathyContact *contact;
1061 tp_contact = empathy_invite_participant_dialog_get_selected (
1062 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1063 if (tp_contact == NULL) goto out;
1065 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1067 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1068 contact, _("Inviting you to this room"));
1070 g_object_unref (contact);
1074 gtk_widget_destroy (dialog);
1078 chat_window_close_activate_cb (GtkAction *action,
1079 EmpathyChatWindow *window)
1081 EmpathyChatWindowPriv *priv;
1083 priv = GET_PRIV (window);
1085 g_return_if_fail (priv->current_chat != NULL);
1087 maybe_close_chat (window, priv->current_chat);
1091 chat_window_edit_activate_cb (GtkAction *action,
1092 EmpathyChatWindow *window)
1094 EmpathyChatWindowPriv *priv;
1095 GtkClipboard *clipboard;
1096 GtkTextBuffer *buffer;
1097 gboolean text_available;
1099 priv = GET_PRIV (window);
1101 g_return_if_fail (priv->current_chat != NULL);
1103 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1104 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1105 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1106 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1110 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1111 if (gtk_text_buffer_get_has_selection (buffer)) {
1112 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1113 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1117 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1119 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1120 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1123 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1124 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1125 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1129 chat_window_cut_activate_cb (GtkAction *action,
1130 EmpathyChatWindow *window)
1132 EmpathyChatWindowPriv *priv;
1134 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1136 priv = GET_PRIV (window);
1138 empathy_chat_cut (priv->current_chat);
1142 chat_window_copy_activate_cb (GtkAction *action,
1143 EmpathyChatWindow *window)
1145 EmpathyChatWindowPriv *priv;
1147 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1149 priv = GET_PRIV (window);
1151 empathy_chat_copy (priv->current_chat);
1155 chat_window_paste_activate_cb (GtkAction *action,
1156 EmpathyChatWindow *window)
1158 EmpathyChatWindowPriv *priv;
1160 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1162 priv = GET_PRIV (window);
1164 empathy_chat_paste (priv->current_chat);
1168 chat_window_find_activate_cb (GtkAction *action,
1169 EmpathyChatWindow *window)
1171 EmpathyChatWindowPriv *priv;
1173 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1175 priv = GET_PRIV (window);
1177 empathy_chat_find (priv->current_chat);
1181 chat_window_tabs_next_activate_cb (GtkAction *action,
1182 EmpathyChatWindow *window)
1184 EmpathyChatWindowPriv *priv;
1185 gint index_, numPages;
1186 gboolean wrap_around;
1188 priv = GET_PRIV (window);
1190 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1191 &wrap_around, NULL);
1193 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1194 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1196 if (index_ == (numPages - 1) && wrap_around) {
1197 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1201 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1205 chat_window_tabs_previous_activate_cb (GtkAction *action,
1206 EmpathyChatWindow *window)
1208 EmpathyChatWindowPriv *priv;
1209 gint index_, numPages;
1210 gboolean wrap_around;
1212 priv = GET_PRIV (window);
1214 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1215 &wrap_around, NULL);
1217 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1218 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1220 if (index_ <= 0 && wrap_around) {
1221 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1225 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1229 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1230 EmpathyChatWindow *window)
1232 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1233 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1234 empathy_get_current_action_time ());
1238 chat_window_tabs_left_activate_cb (GtkAction *action,
1239 EmpathyChatWindow *window)
1241 EmpathyChatWindowPriv *priv;
1243 gint index_, num_pages;
1245 priv = GET_PRIV (window);
1247 chat = priv->current_chat;
1248 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1253 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1257 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1258 chat_window_menu_context_update (priv, num_pages);
1262 chat_window_tabs_right_activate_cb (GtkAction *action,
1263 EmpathyChatWindow *window)
1265 EmpathyChatWindowPriv *priv;
1267 gint index_, num_pages;
1269 priv = GET_PRIV (window);
1271 chat = priv->current_chat;
1272 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1274 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1278 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1279 chat_window_menu_context_update (priv, num_pages);
1282 static EmpathyChatWindow *
1283 empathy_chat_window_new (void)
1285 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1289 chat_window_detach_activate_cb (GtkAction *action,
1290 EmpathyChatWindow *window)
1292 EmpathyChatWindowPriv *priv;
1293 EmpathyChatWindow *new_window;
1296 priv = GET_PRIV (window);
1298 chat = priv->current_chat;
1299 new_window = empathy_chat_window_new ();
1301 empathy_chat_window_move_chat (window, new_window, chat);
1303 priv = GET_PRIV (new_window);
1304 gtk_widget_show (priv->dialog);
1308 chat_window_help_contents_activate_cb (GtkAction *action,
1309 EmpathyChatWindow *window)
1311 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1313 empathy_url_show (priv->dialog, "ghelp:empathy");
1317 chat_window_help_about_activate_cb (GtkAction *action,
1318 EmpathyChatWindow *window)
1320 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1322 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1326 chat_window_delete_event_cb (GtkWidget *dialog,
1328 EmpathyChatWindow *window)
1330 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1332 DEBUG ("Delete event received");
1334 g_object_ref (window);
1335 while (priv->chats) {
1336 empathy_chat_window_remove_chat (window, priv->chats->data);
1338 g_object_unref (window);
1344 chat_window_composing_cb (EmpathyChat *chat,
1345 gboolean is_composing,
1346 EmpathyChatWindow *window)
1348 chat_window_update_chat_tab (chat);
1352 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1355 EmpathyChatWindowPriv *priv;
1357 priv = GET_PRIV (window);
1359 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1363 chat_window_notification_closed_cb (NotifyNotification *notify,
1364 EmpathyChatWindow *self)
1366 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1368 g_object_unref (notify);
1369 if (priv->notification == notify) {
1370 priv->notification = NULL;
1375 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1376 EmpathyMessage *message,
1379 EmpathyContact *sender;
1380 const gchar *header;
1384 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1385 gboolean res, has_x_canonical_append;
1386 NotifyNotification *notification = priv->notification;
1388 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1391 res = g_settings_get_boolean (priv->gsettings_notif,
1392 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1399 sender = empathy_message_get_sender (message);
1400 header = empathy_contact_get_alias (sender);
1401 body = empathy_message_get_body (message);
1402 escaped = g_markup_escape_text (body, -1);
1403 has_x_canonical_append = empathy_notify_manager_has_capability (
1404 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1406 if (notification != NULL && !has_x_canonical_append) {
1407 /* if the notification server supports x-canonical-append, it is
1408 better to not use notify_notification_update to avoid
1409 overwriting the current notification message */
1410 notify_notification_update (notification,
1411 header, escaped, NULL);
1413 /* if the notification server supports x-canonical-append,
1414 the hint will be added, so that the message from the
1415 just created notification will be automatically appended
1416 to an existing notification with the same title.
1417 In this way the previous message will not be lost: the new
1418 message will appear below it, in the same notification */
1419 notification = notify_notification_new (header, escaped, NULL);
1421 if (priv->notification == NULL) {
1422 priv->notification = notification;
1425 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1427 tp_g_signal_connect_object (notification, "closed",
1428 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1430 if (has_x_canonical_append) {
1431 /* We have to set a not empty string to keep libnotify happy */
1432 notify_notification_set_hint_string (notification,
1433 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1436 notify_notification_set_hint (notification,
1437 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1438 g_variant_new_string ("im.received"));
1441 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1442 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1444 if (pixbuf != NULL) {
1445 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1446 g_object_unref (pixbuf);
1449 notify_notification_show (notification, NULL);
1455 chat_window_set_highlight_room_labels (EmpathyChat *chat)
1457 gchar *markup, *name;
1460 if (!empathy_chat_is_room (chat))
1463 name = empathy_chat_dup_name (chat);
1464 markup = g_markup_printf_escaped (
1465 "<span color=\"red\" weight=\"bold\">%s</span>",
1468 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1469 gtk_label_set_markup (GTK_LABEL (widget), markup);
1471 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1472 gtk_label_set_markup (GTK_LABEL (widget), markup);
1479 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1481 EmpathyChatWindowPriv *priv;
1484 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1486 priv = GET_PRIV (window);
1488 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1494 chat_window_new_message_cb (EmpathyChat *chat,
1495 EmpathyMessage *message,
1497 gboolean should_highlight,
1498 EmpathyChatWindow *window)
1500 EmpathyChatWindowPriv *priv;
1502 gboolean needs_urgency;
1503 EmpathyContact *sender;
1505 priv = GET_PRIV (window);
1507 has_focus = empathy_chat_window_has_focus (window);
1509 /* - if we're the sender, we play the sound if it's specified in the
1510 * preferences and we're not away.
1511 * - if we receive a message, we play the sound if it's specified in the
1512 * preferences and the window does not have focus on the chat receiving
1516 sender = empathy_message_get_sender (message);
1518 if (empathy_contact_is_user (sender)) {
1519 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1520 EMPATHY_SOUND_MESSAGE_OUTGOING);
1523 if (has_focus && priv->current_chat == chat) {
1524 /* window and tab are focused so consider the message to be read */
1526 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1527 empathy_chat_messages_read (chat);
1531 /* Update the chat tab if this is the first unread message */
1532 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1533 chat_window_update_chat_tab (chat);
1536 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1537 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1538 * an unamed MUC (msn-like).
1539 * In case of a MUC, we set urgency if either:
1540 * a) the chatroom's always_urgent property is TRUE
1541 * b) the message contains our alias
1543 if (empathy_chat_is_room (chat)) {
1546 EmpathyChatroom *chatroom;
1548 account = empathy_chat_get_account (chat);
1549 room = empathy_chat_get_id (chat);
1551 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1554 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1555 needs_urgency = TRUE;
1557 needs_urgency = should_highlight;
1560 needs_urgency = TRUE;
1563 if (needs_urgency) {
1564 chat_window_set_highlight_room_labels (chat);
1567 chat_window_set_urgency_hint (window, TRUE);
1570 /* Pending messages have already been displayed and notified in the
1571 * approver, so we don't display a notification and play a sound for those */
1573 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1574 EMPATHY_SOUND_MESSAGE_INCOMING);
1576 chat_window_show_or_update_notification (window, message, chat);
1580 /* update the number of unread messages and the window icon */
1581 chat_window_title_update (priv);
1582 chat_window_icon_update (priv, TRUE);
1586 chat_window_command_part (EmpathyChat *chat,
1589 EmpathyChat *chat_to_be_parted;
1590 EmpathyTpChat *tp_chat = NULL;
1592 if (strv[1] == NULL) {
1593 /* No chatroom ID specified */
1594 tp_chat = empathy_chat_get_tp_chat (chat);
1596 empathy_tp_chat_leave (tp_chat, "");
1599 chat_to_be_parted = empathy_chat_window_find_chat (
1600 empathy_chat_get_account (chat), strv[1], FALSE);
1602 if (chat_to_be_parted != NULL) {
1603 /* Found a chatroom matching the specified ID */
1604 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1606 empathy_tp_chat_leave (tp_chat, strv[2]);
1610 /* Going by the syntax of PART command:
1612 * /PART [<chatroom-ID>] [<reason>]
1614 * Chatroom-ID is not a must to specify a reason.
1615 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1616 * MUC then the current chatroom should be parted and srtv[1] should
1617 * be treated as part of the optional part-message. */
1618 message = g_strconcat (strv[1], " ", strv[2], NULL);
1619 tp_chat = empathy_chat_get_tp_chat (chat);
1621 empathy_tp_chat_leave (tp_chat, message);
1627 static GtkNotebook *
1628 notebook_create_window_cb (GtkNotebook *source,
1634 EmpathyChatWindowPriv *priv;
1635 EmpathyChatWindow *window, *new_window;
1638 chat = EMPATHY_CHAT (page);
1639 window = chat_window_find_chat (chat);
1641 new_window = empathy_chat_window_new ();
1642 priv = GET_PRIV (new_window);
1644 DEBUG ("Detach hook called");
1646 empathy_chat_window_move_chat (window, new_window, chat);
1648 gtk_widget_show (priv->dialog);
1649 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1655 chat_window_page_switched_cb (GtkNotebook *notebook,
1658 EmpathyChatWindow *window)
1660 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1661 EmpathyChat *chat = EMPATHY_CHAT (child);
1663 DEBUG ("Page switched");
1665 if (priv->page_added) {
1666 priv->page_added = FALSE;
1667 empathy_chat_scroll_down (chat);
1669 else if (priv->current_chat == chat) {
1673 priv->current_chat = chat;
1674 empathy_chat_messages_read (chat);
1676 chat_window_update_chat_tab (chat);
1680 chat_window_page_added_cb (GtkNotebook *notebook,
1683 EmpathyChatWindow *window)
1685 EmpathyChatWindowPriv *priv;
1688 priv = GET_PRIV (window);
1690 /* If we just received DND to the same window, we don't want
1691 * to do anything here like removing the tab and then readding
1692 * it, so we return here and in "page-added".
1694 if (priv->dnd_same_window) {
1695 DEBUG ("Page added (back to the same window)");
1696 priv->dnd_same_window = FALSE;
1700 DEBUG ("Page added");
1702 /* Get chat object */
1703 chat = EMPATHY_CHAT (child);
1705 /* Connect chat signals for this window */
1706 g_signal_connect (chat, "composing",
1707 G_CALLBACK (chat_window_composing_cb),
1709 g_signal_connect (chat, "new-message",
1710 G_CALLBACK (chat_window_new_message_cb),
1712 g_signal_connect (chat, "part-command-entered",
1713 G_CALLBACK (chat_window_command_part),
1715 g_signal_connect (chat, "notify::tp-chat",
1716 G_CALLBACK (chat_window_update_chat_tab),
1719 /* Set flag so we know to perform some special operations on
1720 * switch page due to the new page being added.
1722 priv->page_added = TRUE;
1724 /* Get list of chats up to date */
1725 priv->chats = g_list_append (priv->chats, chat);
1727 chat_window_update_chat_tab (chat);
1731 chat_window_page_removed_cb (GtkNotebook *notebook,
1734 EmpathyChatWindow *window)
1736 EmpathyChatWindowPriv *priv;
1739 priv = GET_PRIV (window);
1741 /* If we just received DND to the same window, we don't want
1742 * to do anything here like removing the tab and then readding
1743 * it, so we return here and in "page-added".
1745 if (priv->dnd_same_window) {
1746 DEBUG ("Page removed (and will be readded to same window)");
1750 DEBUG ("Page removed");
1752 /* Get chat object */
1753 chat = EMPATHY_CHAT (child);
1755 /* Disconnect all signal handlers for this chat and this window */
1756 g_signal_handlers_disconnect_by_func (chat,
1757 G_CALLBACK (chat_window_composing_cb),
1759 g_signal_handlers_disconnect_by_func (chat,
1760 G_CALLBACK (chat_window_new_message_cb),
1762 g_signal_handlers_disconnect_by_func (chat,
1763 G_CALLBACK (chat_window_update_chat_tab),
1766 /* Keep list of chats up to date */
1767 priv->chats = g_list_remove (priv->chats, chat);
1768 empathy_chat_messages_read (chat);
1770 if (priv->chats == NULL) {
1771 g_object_unref (window);
1773 chat_window_update (window, TRUE);
1778 chat_window_focus_in_event_cb (GtkWidget *widget,
1780 EmpathyChatWindow *window)
1782 EmpathyChatWindowPriv *priv;
1784 priv = GET_PRIV (window);
1786 empathy_chat_messages_read (priv->current_chat);
1788 chat_window_set_urgency_hint (window, FALSE);
1790 /* Update the title, since we now mark all unread messages as read. */
1791 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1797 chat_window_drag_drop (GtkWidget *widget,
1798 GdkDragContext *context,
1802 EmpathyChatWindow *window)
1805 EmpathyChatWindowPriv *priv;
1807 priv = GET_PRIV (window);
1809 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1810 if (target == GDK_NONE)
1811 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1813 if (target != GDK_NONE) {
1814 gtk_drag_get_data (widget, context, target, time_);
1822 chat_window_drag_motion (GtkWidget *widget,
1823 GdkDragContext *context,
1827 EmpathyChatWindow *window)
1830 EmpathyChatWindowPriv *priv;
1832 priv = GET_PRIV (window);
1834 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1835 if (target != GDK_NONE) {
1836 /* This is a file drag. Ensure the contact is online and set the
1837 drag type to COPY. Note that it's possible that the tab will
1838 be switched by GTK+ after a timeout from drag_motion without
1839 getting another drag_motion to disable the drop. You have
1840 to hold your mouse really still.
1842 EmpathyContact *contact;
1844 priv = GET_PRIV (window);
1845 contact = empathy_chat_get_remote_contact (priv->current_chat);
1846 /* contact is NULL for multi-user chats. We don't do
1847 * file transfers to MUCs. We also don't send files
1848 * to offline contacts or contacts that don't support
1851 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1852 gdk_drag_status (context, 0, time_);
1855 if (!(empathy_contact_get_capabilities (contact)
1856 & EMPATHY_CAPABILITIES_FT)) {
1857 gdk_drag_status (context, 0, time_);
1860 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1864 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1865 if (target != GDK_NONE) {
1866 /* This is a drag of a contact from a contact list. Set to COPY.
1867 FIXME: If this drag is to a MUC window, it invites the user.
1868 Otherwise, it opens a chat. Should we use a different drag
1869 type for invites? Should we allow ASK?
1871 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1879 chat_window_drag_data_received (GtkWidget *widget,
1880 GdkDragContext *context,
1883 GtkSelectionData *selection,
1886 EmpathyChatWindow *window)
1888 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1889 EmpathyChat *chat = NULL;
1890 EmpathyChatWindow *old_window;
1891 TpAccount *account = NULL;
1892 EmpathyClientFactory *factory;
1895 const gchar *account_id;
1896 const gchar *contact_id;
1898 id = (const gchar*) gtk_selection_data_get_data (selection);
1900 factory = empathy_client_factory_dup ();
1902 DEBUG ("DND contact from roster with id:'%s'", id);
1904 strv = g_strsplit (id, ":", 2);
1905 if (g_strv_length (strv) == 2) {
1906 account_id = strv[0];
1907 contact_id = strv[1];
1909 tp_simple_client_factory_ensure_account (
1910 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
1913 g_object_unref (factory);
1914 if (account != NULL)
1915 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
1918 if (account == NULL) {
1920 gtk_drag_finish (context, FALSE, FALSE, time_);
1925 empathy_chat_with_contact_id (
1926 account, contact_id,
1927 empathy_get_current_action_time (),
1935 old_window = chat_window_find_chat (chat);
1937 if (old_window == window) {
1938 gtk_drag_finish (context, TRUE, FALSE, time_);
1942 empathy_chat_window_move_chat (old_window, window, chat);
1944 empathy_chat_window_add_chat (window, chat);
1947 /* Added to take care of any outstanding chat events */
1948 empathy_chat_window_present_chat (chat,
1949 TP_USER_ACTION_TIME_NOT_USER_ACTION);
1951 /* We should return TRUE to remove the data when doing
1952 * GDK_ACTION_MOVE, but we don't here otherwise it has
1953 * weird consequences, and we handle that internally
1954 * anyway with add_chat () and remove_chat ().
1956 gtk_drag_finish (context, TRUE, FALSE, time_);
1958 else if (info == DND_DRAG_TYPE_URI_LIST) {
1959 EmpathyChatWindowPriv *priv;
1960 EmpathyContact *contact;
1963 priv = GET_PRIV (window);
1964 contact = empathy_chat_get_remote_contact (priv->current_chat);
1966 /* contact is NULL when current_chat is a multi-user chat.
1967 * We don't do file transfers to MUCs, so just cancel the drag.
1969 if (contact == NULL) {
1970 gtk_drag_finish (context, TRUE, FALSE, time_);
1974 data = (const gchar *) gtk_selection_data_get_data (selection);
1975 empathy_send_file_from_uri_list (contact, data);
1977 gtk_drag_finish (context, TRUE, FALSE, time_);
1979 else if (info == DND_DRAG_TYPE_TAB) {
1981 EmpathyChatWindow *old_window = NULL;
1985 chat = (void *) gtk_selection_data_get_data (selection);
1986 old_window = chat_window_find_chat (*chat);
1989 EmpathyChatWindowPriv *priv;
1991 priv = GET_PRIV (window);
1992 priv->dnd_same_window = (old_window == window);
1993 DEBUG ("DND tab (within same window: %s)",
1994 priv->dnd_same_window ? "Yes" : "No");
1997 DEBUG ("DND from unknown source");
1998 gtk_drag_finish (context, FALSE, FALSE, time_);
2003 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2004 guint num_chats_in_manager,
2005 EmpathyChatWindow *window)
2007 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2009 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2010 num_chats_in_manager > 0);
2014 chat_window_finalize (GObject *object)
2016 EmpathyChatWindow *window;
2017 EmpathyChatWindowPriv *priv;
2019 window = EMPATHY_CHAT_WINDOW (object);
2020 priv = GET_PRIV (window);
2022 DEBUG ("Finalized: %p", object);
2024 g_object_unref (priv->ui_manager);
2025 g_object_unref (priv->chatroom_manager);
2026 g_object_unref (priv->notify_mgr);
2027 g_object_unref (priv->gsettings_chat);
2028 g_object_unref (priv->gsettings_notif);
2029 g_object_unref (priv->gsettings_ui);
2030 g_object_unref (priv->sound_mgr);
2032 if (priv->notification != NULL) {
2033 notify_notification_close (priv->notification, NULL);
2034 priv->notification = NULL;
2037 if (priv->contact_targets) {
2038 gtk_target_list_unref (priv->contact_targets);
2040 if (priv->file_targets) {
2041 gtk_target_list_unref (priv->file_targets);
2044 if (priv->chat_manager) {
2045 g_signal_handler_disconnect (priv->chat_manager,
2046 priv->chat_manager_chats_changed_id);
2047 g_object_unref (priv->chat_manager);
2048 priv->chat_manager = NULL;
2051 chat_windows = g_list_remove (chat_windows, window);
2052 gtk_widget_destroy (priv->dialog);
2054 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2058 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2060 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2062 object_class->finalize = chat_window_finalize;
2064 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2068 empathy_chat_window_init (EmpathyChatWindow *window)
2071 GtkAccelGroup *accel_group;
2076 GtkWidget *chat_vbox;
2078 EmpathySmileyManager *smiley_manager;
2079 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2080 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2082 window->priv = priv;
2083 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2084 gui = empathy_builder_get_file (filename,
2085 "chat_window", &priv->dialog,
2086 "chat_vbox", &chat_vbox,
2087 "ui_manager", &priv->ui_manager,
2088 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2089 "menu_conv_favorite", &priv->menu_conv_favorite,
2090 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2091 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2092 "menu_edit_cut", &priv->menu_edit_cut,
2093 "menu_edit_copy", &priv->menu_edit_copy,
2094 "menu_edit_paste", &priv->menu_edit_paste,
2095 "menu_edit_find", &priv->menu_edit_find,
2096 "menu_tabs_next", &priv->menu_tabs_next,
2097 "menu_tabs_prev", &priv->menu_tabs_prev,
2098 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2099 "menu_tabs_left", &priv->menu_tabs_left,
2100 "menu_tabs_right", &priv->menu_tabs_right,
2101 "menu_tabs_detach", &priv->menu_tabs_detach,
2105 empathy_builder_connect (gui, window,
2106 "menu_conv", "activate", chat_window_conv_activate_cb,
2107 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2108 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2109 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2110 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2111 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2112 "menu_conv_close", "activate", chat_window_close_activate_cb,
2113 "menu_edit", "activate", chat_window_edit_activate_cb,
2114 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2115 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2116 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2117 "menu_edit_find", "activate", chat_window_find_activate_cb,
2118 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2119 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2120 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2121 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2122 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2123 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2124 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2125 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2128 g_object_ref (priv->ui_manager);
2129 g_object_unref (gui);
2131 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2132 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2133 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2134 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2136 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2138 priv->notebook = gtk_notebook_new ();
2140 g_signal_connect (priv->notebook, "create-window",
2141 G_CALLBACK (notebook_create_window_cb), window);
2143 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2144 "EmpathyChatWindow");
2145 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2146 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2147 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2148 gtk_widget_show (priv->notebook);
2151 accel_group = gtk_accel_group_new ();
2152 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2154 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2155 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2158 gtk_accel_group_connect (accel_group,
2165 g_object_unref (accel_group);
2167 /* Set up drag target lists */
2168 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2169 G_N_ELEMENTS (drag_types_dest_contact));
2170 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2171 G_N_ELEMENTS (drag_types_dest_file));
2173 /* Set up smiley menu */
2174 smiley_manager = empathy_smiley_manager_dup_singleton ();
2175 submenu = empathy_smiley_menu_new (smiley_manager,
2176 chat_window_insert_smiley_activate_cb,
2178 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2179 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2180 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2181 g_object_unref (smiley_manager);
2183 /* Set up signals we can't do with ui file since we may need to
2184 * block/unblock them at some later stage.
2187 g_signal_connect (priv->dialog,
2189 G_CALLBACK (chat_window_delete_event_cb),
2191 g_signal_connect (priv->dialog,
2193 G_CALLBACK (chat_window_focus_in_event_cb),
2195 g_signal_connect_after (priv->notebook,
2197 G_CALLBACK (chat_window_page_switched_cb),
2199 g_signal_connect (priv->notebook,
2201 G_CALLBACK (chat_window_page_added_cb),
2203 g_signal_connect (priv->notebook,
2205 G_CALLBACK (chat_window_page_removed_cb),
2208 /* Set up drag and drop */
2209 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2210 GTK_DEST_DEFAULT_HIGHLIGHT,
2212 G_N_ELEMENTS (drag_types_dest),
2213 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2215 /* connect_after to allow GtkNotebook's built-in tab switching */
2216 g_signal_connect_after (priv->notebook,
2218 G_CALLBACK (chat_window_drag_motion),
2220 g_signal_connect (priv->notebook,
2221 "drag-data-received",
2222 G_CALLBACK (chat_window_drag_data_received),
2224 g_signal_connect (priv->notebook,
2226 G_CALLBACK (chat_window_drag_drop),
2229 chat_windows = g_list_prepend (chat_windows, window);
2231 /* Set up private details */
2233 priv->current_chat = NULL;
2234 priv->notification = NULL;
2236 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2238 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2239 priv->chat_manager_chats_changed_id =
2240 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2241 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2244 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2245 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2249 /* Returns the window to open a new tab in if there is a suitable window,
2250 * otherwise, returns NULL indicating that a new window should be added.
2252 static EmpathyChatWindow *
2253 empathy_chat_window_get_default (gboolean room)
2255 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2257 gboolean separate_windows = TRUE;
2259 separate_windows = g_settings_get_boolean (gsettings,
2260 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2262 g_object_unref (gsettings);
2264 if (separate_windows) {
2265 /* Always create a new window */
2269 for (l = chat_windows; l; l = l->next) {
2270 EmpathyChatWindow *chat_window;
2271 guint nb_rooms, nb_private;
2273 chat_window = l->data;
2275 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2277 /* Skip the window if there aren't any rooms in it */
2278 if (room && nb_rooms == 0)
2281 /* Skip the window if there aren't any 1-1 chats in it */
2282 if (!room && nb_private == 0)
2292 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2295 EmpathyChatWindowPriv *priv;
2297 GtkWidget *popup_label;
2299 GValue value = { 0, };
2301 g_return_if_fail (window != NULL);
2302 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2304 priv = GET_PRIV (window);
2306 /* Reference the chat object */
2307 g_object_ref (chat);
2309 /* If this window has just been created, position it */
2310 if (priv->chats == NULL) {
2311 const gchar *name = "chat-window";
2312 gboolean separate_windows;
2314 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2315 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2317 if (empathy_chat_is_room (chat))
2318 name = "room-window";
2320 if (separate_windows) {
2323 /* Save current position of the window */
2324 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2326 /* First bind to the 'generic' name. So new window for which we didn't
2327 * save a geometry yet will have the geometry of the last saved
2328 * window (bgo #601191). */
2329 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2331 /* Restore previous position of the window so the newly created window
2332 * won't be in the same position as the latest saved window and so
2333 * completely hide it. */
2334 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2336 /* Then bind it to the name of the contact/room so we'll save the
2337 * geometry specific to this window */
2338 name = empathy_chat_get_id (chat);
2341 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2344 child = GTK_WIDGET (chat);
2345 label = chat_window_create_label (window, chat, TRUE);
2346 popup_label = chat_window_create_label (window, chat, FALSE);
2347 gtk_widget_show (child);
2349 g_signal_connect (chat, "notify::name",
2350 G_CALLBACK (chat_window_chat_notify_cb),
2352 g_signal_connect (chat, "notify::subject",
2353 G_CALLBACK (chat_window_chat_notify_cb),
2355 g_signal_connect (chat, "notify::remote-contact",
2356 G_CALLBACK (chat_window_chat_notify_cb),
2358 g_signal_connect (chat, "notify::sms-channel",
2359 G_CALLBACK (chat_window_chat_notify_cb),
2361 g_signal_connect (chat, "notify::n-messages-sending",
2362 G_CALLBACK (chat_window_chat_notify_cb),
2364 g_signal_connect (chat, "notify::nb-unread-messages",
2365 G_CALLBACK (chat_window_chat_notify_cb),
2367 chat_window_chat_notify_cb (chat);
2369 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2370 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2371 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2372 g_value_init (&value, G_TYPE_BOOLEAN);
2373 g_value_set_boolean (&value, TRUE);
2374 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2375 child, "tab-expand" , &value);
2376 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2377 child, "tab-fill" , &value);
2378 g_value_unset (&value);
2380 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2384 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2387 EmpathyChatWindowPriv *priv;
2389 EmpathyContact *remote_contact;
2390 EmpathyChatManager *chat_manager;
2392 g_return_if_fail (window != NULL);
2393 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2395 priv = GET_PRIV (window);
2397 g_signal_handlers_disconnect_by_func (chat,
2398 chat_window_chat_notify_cb,
2400 remote_contact = g_object_get_data (G_OBJECT (chat),
2401 "chat-window-remote-contact");
2402 if (remote_contact) {
2403 g_signal_handlers_disconnect_by_func (remote_contact,
2404 chat_window_update_chat_tab,
2408 chat_manager = empathy_chat_manager_dup_singleton ();
2409 empathy_chat_manager_closed_chat (chat_manager, chat);
2410 g_object_unref (chat_manager);
2412 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2414 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2416 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2418 g_object_unref (chat);
2422 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2423 EmpathyChatWindow *new_window,
2428 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2429 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2430 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2432 widget = GTK_WIDGET (chat);
2434 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2435 G_OBJECT (widget)->ref_count);
2437 /* We reference here to make sure we don't loose the widget
2438 * and the EmpathyChat object during the move.
2440 g_object_ref (chat);
2441 g_object_ref (widget);
2443 empathy_chat_window_remove_chat (old_window, chat);
2444 empathy_chat_window_add_chat (new_window, chat);
2446 g_object_unref (widget);
2447 g_object_unref (chat);
2451 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2454 EmpathyChatWindowPriv *priv;
2457 g_return_if_fail (window != NULL);
2458 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2460 priv = GET_PRIV (window);
2462 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2464 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2469 empathy_chat_window_find_chat (TpAccount *account,
2471 gboolean sms_channel)
2475 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2477 for (l = chat_windows; l; l = l->next) {
2478 EmpathyChatWindowPriv *priv;
2479 EmpathyChatWindow *window;
2483 priv = GET_PRIV (window);
2485 for (ll = priv->chats; ll; ll = ll->next) {
2490 if (account == empathy_chat_get_account (chat) &&
2491 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2492 sms_channel == empathy_chat_is_sms_channel (chat)) {
2502 empathy_chat_window_present_chat (EmpathyChat *chat,
2505 EmpathyChatWindow *window;
2506 EmpathyChatWindowPriv *priv;
2507 guint32 x_timestamp;
2509 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2511 window = chat_window_find_chat (chat);
2513 /* If the chat has no window, create one */
2514 if (window == NULL) {
2515 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2517 window = empathy_chat_window_new ();
2519 /* we want to display the newly created window even if we don't present
2521 priv = GET_PRIV (window);
2522 gtk_widget_show (priv->dialog);
2525 empathy_chat_window_add_chat (window, chat);
2528 /* Don't force the window to show itself when it wasn't
2529 * an action by the user
2531 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2534 priv = GET_PRIV (window);
2536 if (x_timestamp != GDK_CURRENT_TIME) {
2537 /* Don't present or switch tab if the action was earlier than the
2538 * last actions X time, accounting for overflow and the first ever
2541 if (priv->x_user_action_time != 0
2542 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2545 priv->x_user_action_time = x_timestamp;
2548 empathy_chat_window_switch_to_chat (window, chat);
2550 /* Don't use empathy_window_present_with_time () which would move the window
2551 * to our current desktop but move to the window's desktop instead. This is
2552 * more coherent with Shell's 'app is ready' notication which moves the view
2553 * to the app desktop rather than moving the app itself. */
2554 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2556 gtk_widget_grab_focus (chat->input_text_view);
2560 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2564 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2566 guint _nb_rooms = 0, _nb_private = 0;
2568 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2569 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2575 if (nb_rooms != NULL)
2576 *nb_rooms = _nb_rooms;
2577 if (nb_private != NULL)
2578 *nb_private = _nb_private;