2 * Copyright (C) 2003-2007 Imendio AB
3 * Copyright (C) 2007-2012 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Richard Hult <richard@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24 * Xavier Claessens <xclaesse@gmail.com>
25 * Rômulo Fernandes Machado <romulo@castorgroup.net>
31 #include <glib/gi18n.h>
33 #include "empathy-client-factory.h"
34 #include "empathy-chatroom-manager.h"
35 #include "empathy-gsettings.h"
36 #include "empathy-utils.h"
37 #include "empathy-request-util.h"
38 #include "empathy-individual-manager.h"
40 #include "empathy-images.h"
41 #include "empathy-geometry.h"
42 #include "empathy-smiley-manager.h"
43 #include "empathy-sound-manager.h"
44 #include "empathy-ui-utils.h"
45 #include "empathy-notify-manager.h"
47 #include "empathy-chat-manager.h"
48 #include "empathy-chat-window.h"
49 #include "empathy-about-dialog.h"
50 #include "empathy-invite-participant-dialog.h"
52 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
53 #include "empathy-debug.h"
55 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
57 #define X_EARLIER_OR_EQL(t1, t2) \
58 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
59 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
64 PROP_INDIVIDUAL_MGR = 1
67 struct _EmpathyChatWindowPriv
69 EmpathyChat *current_chat;
72 gboolean dnd_same_window;
73 EmpathyChatroomManager *chatroom_manager;
74 EmpathyNotifyManager *notify_mgr;
75 EmpathyIndividualManager *individual_mgr;
77 NotifyNotification *notification;
79 GtkTargetList *contact_targets;
80 GtkTargetList *file_targets;
82 EmpathyChatManager *chat_manager;
83 gulong chat_manager_chats_changed_id;
86 GtkUIManager *ui_manager;
87 GtkAction *menu_conv_insert_smiley;
88 GtkAction *menu_conv_favorite;
89 GtkAction *menu_conv_always_urgent;
90 GtkAction *menu_conv_toggle_contacts;
92 GtkAction *menu_edit_cut;
93 GtkAction *menu_edit_copy;
94 GtkAction *menu_edit_paste;
95 GtkAction *menu_edit_find;
97 GtkAction *menu_tabs_next;
98 GtkAction *menu_tabs_prev;
99 GtkAction *menu_tabs_undo_close_tab;
100 GtkAction *menu_tabs_left;
101 GtkAction *menu_tabs_right;
102 GtkAction *menu_tabs_detach;
104 /* Last user action time we acted upon to show a tab */
105 guint32 x_user_action_time;
107 GSettings *gsettings_chat;
108 GSettings *gsettings_notif;
109 GSettings *gsettings_ui;
111 EmpathySoundManager *sound_mgr;
113 gboolean updating_menu;
116 static GList *chat_windows = NULL;
118 static const guint tab_accel_keys[] =
120 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
121 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
126 DND_DRAG_TYPE_CONTACT_ID,
127 DND_DRAG_TYPE_INDIVIDUAL_ID,
128 DND_DRAG_TYPE_URI_LIST,
132 static const GtkTargetEntry drag_types_dest[] =
134 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
135 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
136 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
137 /* FIXME: disabled because of bug #640513
138 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
139 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] =
145 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
146 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
149 static const GtkTargetEntry drag_types_dest_file[] =
151 /* must be first to be prioritized, in order to receive the
152 * note's file path from Tomboy instead of an URI */
153 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
154 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
157 static void chat_window_update (EmpathyChatWindow *window,
158 gboolean update_contact_menu);
160 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
163 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
166 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
167 EmpathyChatWindow *new_window,
170 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
174 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_WINDOW)
177 chat_window_accel_cb (GtkAccelGroup *accelgroup,
181 EmpathyChatWindow *self)
186 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
188 if (tab_accel_keys[i] == key)
196 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
199 static EmpathyChatWindow *
200 chat_window_find_chat (EmpathyChat *chat)
204 for (l = chat_windows; l; l = l->next)
206 EmpathyChatWindow *window = l->data;
208 ll = g_list_find (window->priv->chats, chat);
217 remove_all_chats (EmpathyChatWindow *self)
221 while (self->priv->chats)
222 empathy_chat_window_remove_chat (self, self->priv->chats->data);
224 g_object_unref (self);
228 confirm_close_response_cb (GtkWidget *dialog,
230 EmpathyChatWindow *window)
234 chat = g_object_get_data (G_OBJECT (dialog), "chat");
236 gtk_widget_destroy (dialog);
238 if (response != GTK_RESPONSE_ACCEPT)
242 empathy_chat_window_remove_chat (window, chat);
244 remove_all_chats (window);
248 confirm_close (EmpathyChatWindow *self,
249 gboolean close_window,
254 gchar *primary, *secondary;
256 g_return_if_fail (n_rooms > 0);
259 g_return_if_fail (chat == NULL);
261 g_return_if_fail (chat != NULL);
263 /* If there are no chats in this window, how could we possibly have got
266 g_return_if_fail (self->priv->chats != NULL);
268 /* Treat closing a window which only has one tab exactly like closing
271 if (close_window && self->priv->chats->next == NULL)
273 close_window = FALSE;
274 chat = self->priv->chats->data;
279 primary = g_strdup (_("Close this window?"));
283 gchar *chat_name = empathy_chat_dup_name (chat);
284 secondary = g_strdup_printf (
285 _("Closing this window will leave %s. You will "
286 "not receive any further messages until you "
293 secondary = g_strdup_printf (
294 /* Note to translators: the number of chats will
295 * always be at least 2.
298 "Closing this window will leave a chat room. You will "
299 "not receive any further messages until you rejoin it.",
300 "Closing this window will leave %u chat rooms. You will "
301 "not receive any further messages until you rejoin them.",
308 gchar *chat_name = empathy_chat_dup_name (chat);
309 primary = g_strdup_printf (_("Leave %s?"), chat_name);
310 secondary = g_strdup (
311 _("You will not receive any further messages from this chat "
312 "room until you rejoin it."));
316 dialog = gtk_message_dialog_new (
318 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
323 gtk_window_set_title (GTK_WINDOW (dialog), "");
324 g_object_set (dialog, "secondary-text", secondary, NULL);
329 gtk_dialog_add_button (GTK_DIALOG (dialog),
330 close_window ? _("Close window") : _("Leave room"),
331 GTK_RESPONSE_ACCEPT);
332 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
333 GTK_RESPONSE_ACCEPT);
336 g_object_set_data (G_OBJECT (dialog), "chat", chat);
338 g_signal_connect (dialog, "response",
339 G_CALLBACK (confirm_close_response_cb), self);
341 gtk_window_present (GTK_WINDOW (dialog));
344 /* Returns TRUE if we should check if the user really wants to leave. If it's
345 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
346 * the user is actually in the room as opposed to having been kicked or gone
347 * offline or something), then we should check.
350 chat_needs_close_confirmation (EmpathyChat *chat)
352 return (empathy_chat_is_room (chat) &&
353 empathy_chat_get_tp_chat (chat) != NULL);
357 maybe_close_chat (EmpathyChatWindow *window,
360 g_return_if_fail (chat != NULL);
362 if (chat_needs_close_confirmation (chat))
363 confirm_close (window, FALSE, 1, chat);
365 empathy_chat_window_remove_chat (window, chat);
369 chat_window_close_clicked_cb (GtkAction *action,
372 EmpathyChatWindow *window;
374 window = chat_window_find_chat (chat);
375 maybe_close_chat (window, chat);
379 chat_tab_style_updated_cb (GtkWidget *hbox,
383 int char_width, h, w;
384 PangoContext *context;
385 const PangoFontDescription *font_desc;
386 PangoFontMetrics *metrics;
388 button = g_object_get_data (G_OBJECT (user_data),
389 "chat-window-tab-close-button");
390 context = gtk_widget_get_pango_context (hbox);
392 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
393 GTK_STATE_FLAG_NORMAL);
395 metrics = pango_context_get_metrics (context, font_desc,
396 pango_context_get_language (context));
397 char_width = pango_font_metrics_get_approximate_char_width (metrics);
398 pango_font_metrics_unref (metrics);
400 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
401 GTK_ICON_SIZE_MENU, &w, &h);
403 /* Request at least about 12 chars width plus at least space for the status
404 * image and the close button */
405 gtk_widget_set_size_request (hbox,
406 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
408 gtk_widget_set_size_request (button, w, h);
412 create_close_button (void)
414 GtkWidget *button, *image;
415 GtkStyleContext *context;
417 button = gtk_button_new ();
419 context = gtk_widget_get_style_context (button);
420 gtk_style_context_add_class (context, "empathy-tab-close-button");
422 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
423 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
425 /* We don't want focus/keynav for the button to avoid clutter, and
426 * Ctrl-W works anyway.
428 gtk_widget_set_can_focus (button, FALSE);
429 gtk_widget_set_can_default (button, FALSE);
431 image = gtk_image_new_from_icon_name ("window-close-symbolic",
433 gtk_widget_show (image);
435 gtk_container_add (GTK_CONTAINER (button), image);
441 chat_window_create_label (EmpathyChatWindow *window,
443 gboolean is_tab_label)
446 GtkWidget *name_label;
447 GtkWidget *status_image;
448 GtkWidget *event_box;
449 GtkWidget *event_box_hbox;
450 PangoAttrList *attr_list;
451 PangoAttribute *attr;
453 /* The spacing between the button and the label. */
454 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
456 event_box = gtk_event_box_new ();
457 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
459 name_label = gtk_label_new (NULL);
461 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
463 attr_list = pango_attr_list_new ();
464 attr = pango_attr_scale_new (1/1.2);
465 attr->start_index = 0;
466 attr->end_index = -1;
467 pango_attr_list_insert (attr_list, attr);
468 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
469 pango_attr_list_unref (attr_list);
471 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
472 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
473 g_object_set_data (G_OBJECT (chat),
474 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
477 status_image = gtk_image_new ();
479 /* Spacing between the icon and label. */
480 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
482 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
483 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
485 g_object_set_data (G_OBJECT (chat),
486 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
488 g_object_set_data (G_OBJECT (chat),
489 is_tab_label ? "chat-window-tab-tooltip-widget" :
490 "chat-window-menu-tooltip-widget",
493 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
494 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
498 GtkWidget *close_button;
499 GtkWidget *sending_spinner;
501 sending_spinner = gtk_spinner_new ();
503 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
505 g_object_set_data (G_OBJECT (chat),
506 "chat-window-tab-sending-spinner",
509 close_button = create_close_button ();
510 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
513 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
515 g_signal_connect (close_button,
517 G_CALLBACK (chat_window_close_clicked_cb), chat);
519 /* React to theme changes and also setup the size correctly. */
520 g_signal_connect (hbox, "style-updated",
521 G_CALLBACK (chat_tab_style_updated_cb), chat);
524 gtk_widget_show_all (hbox);
530 _submenu_notify_visible_changed_cb (GObject *object,
534 g_signal_handlers_disconnect_by_func (object,
535 _submenu_notify_visible_changed_cb, userdata);
537 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
541 chat_window_menu_context_update (EmpathyChatWindow *self,
546 gboolean wrap_around;
547 gboolean is_connected;
550 page_num = gtk_notebook_get_current_page (
551 GTK_NOTEBOOK (self->priv->notebook));
552 first_page = (page_num == 0);
553 last_page = (page_num == (num_pages - 1));
554 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
556 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
558 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
560 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
562 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
563 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
564 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
565 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
569 chat_window_conversation_menu_update (EmpathyChatWindow *self)
571 EmpathyTpChat *tp_chat;
572 TpConnection *connection;
574 gboolean sensitive = FALSE;
576 g_return_if_fail (self->priv->current_chat != NULL);
578 action = gtk_ui_manager_get_action (self->priv->ui_manager,
579 "/chats_menubar/menu_conv/menu_conv_invite_participant");
580 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
584 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
586 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
587 (tp_connection_get_status (connection, NULL) ==
588 TP_CONNECTION_STATUS_CONNECTED);
591 gtk_action_set_sensitive (action, sensitive);
595 chat_window_contact_menu_update (EmpathyChatWindow *self)
597 GtkWidget *menu, *submenu, *orig_submenu;
599 if (self->priv->updating_menu)
601 self->priv->updating_menu = TRUE;
603 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
604 "/chats_menubar/menu_contact");
605 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
607 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
609 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
613 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
614 g_object_set_data (G_OBJECT (submenu), "window", self);
616 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
617 gtk_widget_show (menu);
618 gtk_widget_set_sensitive (menu, TRUE);
622 gtk_widget_set_sensitive (menu, FALSE);
627 tp_g_signal_connect_object (orig_submenu,
629 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
632 self->priv->updating_menu = FALSE;
636 get_all_unread_messages (EmpathyChatWindow *self)
641 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
642 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
648 get_window_title_name (EmpathyChatWindow *self)
650 gchar *active_name, *ret;
652 guint current_unread_msgs;
654 nb_chats = g_list_length (self->priv->chats);
655 g_assert (nb_chats > 0);
657 active_name = empathy_chat_dup_name (self->priv->current_chat);
659 current_unread_msgs = empathy_chat_get_nb_unread_messages (
660 self->priv->current_chat);
665 if (current_unread_msgs == 0)
666 ret = g_strdup (active_name);
668 ret = g_strdup_printf (ngettext (
670 "%s (%d unread)", current_unread_msgs),
671 active_name, current_unread_msgs);
675 guint nb_others = nb_chats - 1;
676 guint all_unread_msgs;
678 all_unread_msgs = get_all_unread_messages (self);
680 if (all_unread_msgs == 0)
682 /* no unread message */
683 ret = g_strdup_printf (ngettext (
685 "%s (and %u others)", nb_others),
686 active_name, nb_others);
688 else if (all_unread_msgs == current_unread_msgs)
690 /* unread messages are in the current tab */
691 ret = g_strdup_printf (ngettext (
693 "%s (%d unread)", current_unread_msgs),
694 active_name, current_unread_msgs);
696 else if (current_unread_msgs == 0)
698 /* unread messages are in other tabs */
699 ret = g_strdup_printf (ngettext (
700 "%s (%d unread from others)",
701 "%s (%d unread from others)",
703 active_name, all_unread_msgs);
707 /* unread messages are in all the tabs */
708 ret = g_strdup_printf (ngettext (
709 "%s (%d unread from all)",
710 "%s (%d unread from all)",
712 active_name, all_unread_msgs);
716 g_free (active_name);
722 chat_window_title_update (EmpathyChatWindow *self)
726 name = get_window_title_name (self);
727 gtk_window_set_title (GTK_WINDOW (self), name);
732 chat_window_icon_update (EmpathyChatWindow *self,
733 gboolean new_messages)
736 EmpathyContact *remote_contact;
737 gboolean avatar_in_icon;
740 n_chats = g_list_length (self->priv->chats);
742 /* Update window icon */
745 gtk_window_set_icon_name (GTK_WINDOW (self),
746 EMPATHY_IMAGE_MESSAGE);
750 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
751 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
753 if (n_chats == 1 && avatar_in_icon)
755 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
756 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
758 gtk_window_set_icon (GTK_WINDOW (self), icon);
761 g_object_unref (icon);
765 gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
771 chat_window_close_button_update (EmpathyChatWindow *self,
775 GtkWidget *chat_close_button;
780 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
781 chat_close_button = g_object_get_data (G_OBJECT (chat),
782 "chat-window-tab-close-button");
783 gtk_widget_hide (chat_close_button);
787 for (i=0; i<num_pages; i++)
789 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
790 chat_close_button = g_object_get_data (G_OBJECT (chat),
791 "chat-window-tab-close-button");
792 gtk_widget_show (chat_close_button);
798 chat_window_update (EmpathyChatWindow *self,
799 gboolean update_contact_menu)
803 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
805 /* Update Tab menu */
806 chat_window_menu_context_update (self, num_pages);
808 chat_window_conversation_menu_update (self);
810 /* If this update is due to a focus-in event, we know the menu will be
811 the same as when we last left it, so no work to do. Besides, if we
812 swap out the menu on a focus-in, we may confuse any external global
814 if (update_contact_menu)
816 chat_window_contact_menu_update (self);
819 chat_window_title_update (self);
821 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
823 chat_window_close_button_update (self, num_pages);
827 append_markup_printf (GString *string,
834 va_start (args, format);
836 tmp = g_markup_vprintf_escaped (format, args);
837 g_string_append (string, tmp);
844 chat_window_update_chat_tab_full (EmpathyChat *chat,
845 gboolean update_contact_menu)
847 EmpathyChatWindow *self;
848 EmpathyContact *remote_contact;
852 const gchar *subject;
853 const gchar *status = NULL;
857 const gchar *icon_name;
858 GtkWidget *tab_image;
859 GtkWidget *menu_image;
860 GtkWidget *sending_spinner;
863 self = chat_window_find_chat (chat);
867 /* Get information */
868 name = empathy_chat_dup_name (chat);
869 account = empathy_chat_get_account (chat);
870 subject = empathy_chat_get_subject (chat);
871 remote_contact = empathy_chat_get_remote_contact (chat);
873 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
875 name, tp_proxy_get_object_path (account), subject, remote_contact);
877 /* Update tab image */
878 if (empathy_chat_get_tp_chat (chat) == NULL)
880 /* No TpChat, we are disconnected */
883 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
885 icon_name = EMPATHY_IMAGE_MESSAGE;
887 else if (remote_contact && empathy_chat_is_composing (chat))
889 icon_name = EMPATHY_IMAGE_TYPING;
891 else if (empathy_chat_is_sms_channel (chat))
893 icon_name = EMPATHY_IMAGE_SMS;
895 else if (remote_contact)
897 icon_name = empathy_icon_name_for_contact (remote_contact);
901 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
904 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
905 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
907 if (icon_name != NULL)
909 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
911 gtk_widget_show (tab_image);
912 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
914 gtk_widget_show (menu_image);
918 gtk_widget_hide (tab_image);
919 gtk_widget_hide (menu_image);
922 /* Update the sending spinner */
923 nb_sending = empathy_chat_get_n_messages_sending (chat);
924 sending_spinner = g_object_get_data (G_OBJECT (chat),
925 "chat-window-tab-sending-spinner");
927 g_object_set (sending_spinner,
928 "active", nb_sending > 0,
929 "visible", nb_sending > 0,
932 /* Update tab tooltip */
933 tooltip = g_string_new (NULL);
937 id = empathy_contact_get_id (remote_contact);
938 status = empathy_contact_get_presence_message (remote_contact);
945 if (empathy_chat_is_sms_channel (chat))
946 append_markup_printf (tooltip, "%s ", _("SMS:"));
948 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
949 id, tp_account_get_display_name (account));
953 char *tmp = g_strdup_printf (
954 ngettext ("Sending %d message",
955 "Sending %d messages",
959 g_string_append (tooltip, "\n");
960 g_string_append (tooltip, tmp);
962 gtk_widget_set_tooltip_text (sending_spinner, tmp);
966 if (!EMP_STR_EMPTY (status))
967 append_markup_printf (tooltip, "\n<i>%s</i>", status);
969 if (!EMP_STR_EMPTY (subject))
970 append_markup_printf (tooltip, "\n<b>%s</b> %s",
971 _("Topic:"), subject);
973 if (remote_contact && empathy_chat_is_composing (chat))
974 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
976 if (remote_contact != NULL)
978 const gchar * const *types;
980 types = empathy_contact_get_client_types (remote_contact);
981 if (empathy_client_types_contains_mobile_device ((GStrv) types))
983 /* I'm on a mobile device ! */
986 name = g_strdup_printf ("☎ %s", name);
991 markup = g_string_free (tooltip, FALSE);
992 widget = g_object_get_data (G_OBJECT (chat),
993 "chat-window-tab-tooltip-widget");
994 gtk_widget_set_tooltip_markup (widget, markup);
996 widget = g_object_get_data (G_OBJECT (chat),
997 "chat-window-menu-tooltip-widget");
998 gtk_widget_set_tooltip_markup (widget, markup);
1001 /* Update tab and menu label */
1002 if (empathy_chat_is_highlighted (chat))
1004 markup = g_markup_printf_escaped (
1005 "<span color=\"red\" weight=\"bold\">%s</span>",
1010 markup = g_markup_escape_text (name, -1);
1013 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1014 gtk_label_set_markup (GTK_LABEL (widget), markup);
1015 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1016 gtk_label_set_markup (GTK_LABEL (widget), markup);
1019 /* Update the window if it's the current chat */
1020 if (self->priv->current_chat == chat)
1021 chat_window_update (self, update_contact_menu);
1027 chat_window_update_chat_tab (EmpathyChat *chat)
1029 chat_window_update_chat_tab_full (chat, TRUE);
1033 chat_window_chat_notify_cb (EmpathyChat *chat)
1035 EmpathyChatWindow *window;
1036 EmpathyContact *old_remote_contact;
1037 EmpathyContact *remote_contact = NULL;
1039 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1040 "chat-window-remote-contact");
1041 remote_contact = empathy_chat_get_remote_contact (chat);
1043 if (old_remote_contact != remote_contact)
1045 /* The remote-contact associated with the chat changed, we need
1046 * to keep track of any change of that contact and update the
1047 * window each time. */
1049 g_signal_connect_swapped (remote_contact, "notify",
1050 G_CALLBACK (chat_window_update_chat_tab), chat);
1052 if (old_remote_contact)
1053 g_signal_handlers_disconnect_by_func (old_remote_contact,
1054 chat_window_update_chat_tab, chat);
1056 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1057 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1060 chat_window_update_chat_tab (chat);
1062 window = chat_window_find_chat (chat);
1064 chat_window_update (window, FALSE);
1068 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1069 EmpathySmiley *smiley,
1072 EmpathyChatWindow *self = user_data;
1074 GtkTextBuffer *buffer;
1077 chat = self->priv->current_chat;
1079 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1080 gtk_text_buffer_get_end_iter (buffer, &iter);
1081 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1085 chat_window_conv_activate_cb (GtkAction *action,
1086 EmpathyChatWindow *self)
1090 EmpathyContact *remote_contact = NULL;
1092 /* Favorite room menu */
1093 is_room = empathy_chat_is_room (self->priv->current_chat);
1098 gboolean found = FALSE;
1099 EmpathyChatroom *chatroom;
1101 room = empathy_chat_get_id (self->priv->current_chat);
1102 account = empathy_chat_get_account (self->priv->current_chat);
1103 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1106 if (chatroom != NULL)
1107 found = empathy_chatroom_is_favorite (chatroom);
1109 DEBUG ("This room %s favorite", found ? "is" : "is not");
1110 gtk_toggle_action_set_active (
1111 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1113 if (chatroom != NULL)
1114 found = empathy_chatroom_is_always_urgent (chatroom);
1116 gtk_toggle_action_set_active (
1117 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1120 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1121 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1123 /* Show contacts menu */
1124 g_object_get (self->priv->current_chat,
1125 "remote-contact", &remote_contact,
1126 "show-contacts", &active,
1129 if (remote_contact == NULL)
1131 gtk_toggle_action_set_active (
1132 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1135 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1136 (remote_contact == NULL));
1138 if (remote_contact != NULL)
1139 g_object_unref (remote_contact);
1143 chat_window_clear_activate_cb (GtkAction *action,
1144 EmpathyChatWindow *self)
1146 empathy_chat_clear (self->priv->current_chat);
1150 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1151 EmpathyChatWindow *self)
1157 EmpathyChatroom *chatroom;
1159 active = gtk_toggle_action_get_active (toggle_action);
1160 account = empathy_chat_get_account (self->priv->current_chat);
1161 room = empathy_chat_get_id (self->priv->current_chat);
1162 name = empathy_chat_dup_name (self->priv->current_chat);
1164 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1165 account, room, name);
1167 empathy_chatroom_set_favorite (chatroom, active);
1168 g_object_unref (chatroom);
1173 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1174 EmpathyChatWindow *self)
1180 EmpathyChatroom *chatroom;
1182 active = gtk_toggle_action_get_active (toggle_action);
1183 account = empathy_chat_get_account (self->priv->current_chat);
1184 room = empathy_chat_get_id (self->priv->current_chat);
1185 name = empathy_chat_dup_name (self->priv->current_chat);
1187 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1188 account, room, name);
1190 empathy_chatroom_set_always_urgent (chatroom, active);
1191 g_object_unref (chatroom);
1196 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1197 EmpathyChatWindow *self)
1201 active = gtk_toggle_action_get_active (toggle_action);
1203 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1207 chat_window_invite_participant_activate_cb (GtkAction *action,
1208 EmpathyChatWindow *self)
1211 EmpathyTpChat *tp_chat;
1214 g_return_if_fail (self->priv->current_chat != NULL);
1216 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1218 dialog = empathy_invite_participant_dialog_new (
1219 GTK_WINDOW (self), tp_chat);
1221 gtk_widget_show (dialog);
1223 response = gtk_dialog_run (GTK_DIALOG (dialog));
1225 if (response == GTK_RESPONSE_ACCEPT)
1227 TpContact *tp_contact;
1228 EmpathyContact *contact;
1230 tp_contact = empathy_invite_participant_dialog_get_selected (
1231 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1232 if (tp_contact == NULL)
1235 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1237 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1239 g_object_unref (contact);
1243 gtk_widget_destroy (dialog);
1247 chat_window_close_activate_cb (GtkAction *action,
1248 EmpathyChatWindow *self)
1250 g_return_if_fail (self->priv->current_chat != NULL);
1252 maybe_close_chat (self, self->priv->current_chat);
1256 chat_window_edit_activate_cb (GtkAction *action,
1257 EmpathyChatWindow *self)
1259 GtkClipboard *clipboard;
1260 GtkTextBuffer *buffer;
1261 gboolean text_available;
1263 g_return_if_fail (self->priv->current_chat != NULL);
1265 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1267 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1268 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1269 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1273 buffer = gtk_text_view_get_buffer (
1274 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1276 if (gtk_text_buffer_get_has_selection (buffer))
1278 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1279 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1285 selection = empathy_theme_adium_get_has_selection (
1286 self->priv->current_chat->view);
1288 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1289 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1292 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1293 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1294 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1298 chat_window_cut_activate_cb (GtkAction *action,
1299 EmpathyChatWindow *self)
1301 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1303 empathy_chat_cut (self->priv->current_chat);
1307 chat_window_copy_activate_cb (GtkAction *action,
1308 EmpathyChatWindow *self)
1310 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1312 empathy_chat_copy (self->priv->current_chat);
1316 chat_window_paste_activate_cb (GtkAction *action,
1317 EmpathyChatWindow *self)
1319 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1321 empathy_chat_paste (self->priv->current_chat);
1325 chat_window_find_activate_cb (GtkAction *action,
1326 EmpathyChatWindow *self)
1328 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1330 empathy_chat_find (self->priv->current_chat);
1334 chat_window_tabs_next_activate_cb (GtkAction *action,
1335 EmpathyChatWindow *self)
1337 gint index_, numPages;
1338 gboolean wrap_around;
1340 g_object_get (gtk_settings_get_default (),
1341 "gtk-keynav-wrap-around", &wrap_around,
1344 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1345 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1347 if (index_ == (numPages - 1) && wrap_around)
1349 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1353 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1357 chat_window_tabs_previous_activate_cb (GtkAction *action,
1358 EmpathyChatWindow *self)
1360 gint index_, numPages;
1361 gboolean wrap_around;
1363 g_object_get (gtk_settings_get_default (),
1364 "gtk-keynav-wrap-around", &wrap_around,
1367 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1368 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1370 if (index_ <= 0 && wrap_around)
1372 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1377 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1381 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1382 EmpathyChatWindow *self)
1384 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1385 empathy_get_current_action_time ());
1389 chat_window_tabs_left_activate_cb (GtkAction *action,
1390 EmpathyChatWindow *self)
1393 gint index_, num_pages;
1395 chat = self->priv->current_chat;
1396 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1400 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1403 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1404 chat_window_menu_context_update (self, num_pages);
1408 chat_window_tabs_right_activate_cb (GtkAction *action,
1409 EmpathyChatWindow *self)
1412 gint index_, num_pages;
1414 chat = self->priv->current_chat;
1415 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1417 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1420 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1421 chat_window_menu_context_update (self, num_pages);
1424 static EmpathyChatWindow *
1425 empathy_chat_window_new (void)
1427 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1428 "default-width", 580,
1429 "default-height", 480,
1436 chat_window_detach_activate_cb (GtkAction *action,
1437 EmpathyChatWindow *self)
1439 EmpathyChatWindow *new_window;
1442 chat = self->priv->current_chat;
1443 new_window = empathy_chat_window_new ();
1445 empathy_chat_window_move_chat (self, new_window, chat);
1447 gtk_widget_show (GTK_WIDGET (new_window));
1451 chat_window_help_contents_activate_cb (GtkAction *action,
1452 EmpathyChatWindow *self)
1454 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1458 chat_window_help_about_activate_cb (GtkAction *action,
1459 EmpathyChatWindow *self)
1461 empathy_about_dialog_new (GTK_WINDOW (self));
1465 chat_window_delete_event_cb (GtkWidget *dialog,
1467 EmpathyChatWindow *self)
1469 EmpathyChat *chat = NULL;
1473 DEBUG ("Delete event received");
1475 for (l = self->priv->chats; l != NULL; l = l->next)
1477 if (chat_needs_close_confirmation (l->data))
1486 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1490 remove_all_chats (self);
1497 chat_window_composing_cb (EmpathyChat *chat,
1498 gboolean is_composing,
1499 EmpathyChatWindow *self)
1501 chat_window_update_chat_tab (chat);
1505 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1508 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1512 chat_window_notification_closed_cb (NotifyNotification *notify,
1513 EmpathyChatWindow *self)
1515 g_object_unref (notify);
1516 if (self->priv->notification == notify)
1517 self->priv->notification = NULL;
1521 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1522 EmpathyMessage *message,
1525 EmpathyContact *sender;
1526 const gchar *header;
1530 gboolean res, has_x_canonical_append;
1531 NotifyNotification *notification = self->priv->notification;
1533 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1536 res = g_settings_get_boolean (self->priv->gsettings_notif,
1537 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1542 sender = empathy_message_get_sender (message);
1543 header = empathy_contact_get_alias (sender);
1544 body = empathy_message_get_body (message);
1545 escaped = g_markup_escape_text (body, -1);
1547 has_x_canonical_append = empathy_notify_manager_has_capability (
1548 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1550 if (notification != NULL && !has_x_canonical_append)
1552 /* if the notification server supports x-canonical-append, it is
1553 better to not use notify_notification_update to avoid
1554 overwriting the current notification message */
1555 notify_notification_update (notification,
1556 header, escaped, NULL);
1560 /* if the notification server supports x-canonical-append,
1561 the hint will be added, so that the message from the
1562 just created notification will be automatically appended
1563 to an existing notification with the same title.
1564 In this way the previous message will not be lost: the new
1565 message will appear below it, in the same notification */
1566 const gchar *category = empathy_chat_is_room (chat)
1567 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1568 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1570 notification = empathy_notify_manager_create_notification (header,
1573 if (self->priv->notification == NULL)
1574 self->priv->notification = notification;
1576 tp_g_signal_connect_object (notification, "closed",
1577 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1579 if (has_x_canonical_append)
1581 /* We have to set a not empty string to keep libnotify happy */
1582 notify_notification_set_hint_string (notification,
1583 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1586 notify_notification_set_hint (notification,
1587 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1590 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1591 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1595 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1596 g_object_unref (pixbuf);
1599 notify_notification_show (notification, NULL);
1605 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1609 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1611 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1617 chat_window_new_message_cb (EmpathyChat *chat,
1618 EmpathyMessage *message,
1620 gboolean should_highlight,
1621 EmpathyChatWindow *self)
1624 gboolean needs_urgency;
1625 EmpathyContact *sender;
1627 has_focus = empathy_chat_window_has_focus (self);
1629 /* - if we're the sender, we play the sound if it's specified in the
1630 * preferences and we're not away.
1631 * - if we receive a message, we play the sound if it's specified in the
1632 * preferences and the window does not have focus on the chat receiving
1636 sender = empathy_message_get_sender (message);
1638 if (empathy_contact_is_user (sender))
1640 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1641 EMPATHY_SOUND_MESSAGE_OUTGOING);
1645 if (has_focus && self->priv->current_chat == chat)
1647 /* window and tab are focused so consider the message to be read */
1649 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1650 empathy_chat_messages_read (chat);
1654 /* Update the chat tab if this is the first unread message */
1655 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1657 chat_window_update_chat_tab (chat);
1660 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1661 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1662 * an unamed MUC (msn-like).
1663 * In case of a MUC, we set urgency if either:
1664 * a) the chatroom's always_urgent property is TRUE
1665 * b) the message contains our alias
1667 if (empathy_chat_is_room (chat))
1671 EmpathyChatroom *chatroom;
1673 account = empathy_chat_get_account (chat);
1674 room = empathy_chat_get_id (chat);
1676 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1679 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1680 needs_urgency = TRUE;
1682 needs_urgency = should_highlight;
1686 needs_urgency = TRUE;
1692 chat_window_set_urgency_hint (self, TRUE);
1694 /* Pending messages have already been displayed and notified in the
1695 * approver, so we don't display a notification and play a sound
1699 empathy_sound_manager_play (self->priv->sound_mgr,
1700 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1702 chat_window_show_or_update_notification (self, message, chat);
1706 /* update the number of unread messages and the window icon */
1707 chat_window_title_update (self);
1708 chat_window_icon_update (self, TRUE);
1712 chat_window_command_part (EmpathyChat *chat,
1715 EmpathyChat *chat_to_be_parted;
1716 EmpathyTpChat *tp_chat = NULL;
1718 if (strv[1] == NULL)
1720 /* No chatroom ID specified */
1721 tp_chat = empathy_chat_get_tp_chat (chat);
1724 empathy_tp_chat_leave (tp_chat, "");
1729 chat_to_be_parted = empathy_chat_window_find_chat (
1730 empathy_chat_get_account (chat), strv[1], FALSE);
1732 if (chat_to_be_parted != NULL)
1734 /* Found a chatroom matching the specified ID */
1735 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1738 empathy_tp_chat_leave (tp_chat, strv[2]);
1744 /* Going by the syntax of PART command:
1746 * /PART [<chatroom-ID>] [<reason>]
1748 * Chatroom-ID is not a must to specify a reason.
1749 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1750 * MUC then the current chatroom should be parted and srtv[1] should
1751 * be treated as part of the optional part-message. */
1752 message = g_strconcat (strv[1], " ", strv[2], NULL);
1753 tp_chat = empathy_chat_get_tp_chat (chat);
1756 empathy_tp_chat_leave (tp_chat, message);
1762 static GtkNotebook *
1763 notebook_create_window_cb (GtkNotebook *source,
1769 EmpathyChatWindow *window, *new_window;
1772 chat = EMPATHY_CHAT (page);
1773 window = chat_window_find_chat (chat);
1775 new_window = empathy_chat_window_new ();
1777 DEBUG ("Detach hook called");
1779 empathy_chat_window_move_chat (window, new_window, chat);
1781 gtk_widget_show (GTK_WIDGET (new_window));
1782 gtk_window_move (GTK_WINDOW (new_window), x, y);
1788 chat_window_page_switched_cb (GtkNotebook *notebook,
1791 EmpathyChatWindow *self)
1793 EmpathyChat *chat = EMPATHY_CHAT (child);
1795 DEBUG ("Page switched");
1797 if (self->priv->page_added)
1799 self->priv->page_added = FALSE;
1800 empathy_chat_scroll_down (chat);
1802 else if (self->priv->current_chat == chat)
1807 self->priv->current_chat = chat;
1808 empathy_chat_messages_read (chat);
1810 chat_window_update_chat_tab (chat);
1814 chat_window_page_added_cb (GtkNotebook *notebook,
1817 EmpathyChatWindow *self)
1821 /* If we just received DND to the same window, we don't want
1822 * to do anything here like removing the tab and then readding
1823 * it, so we return here and in "page-added".
1825 if (self->priv->dnd_same_window)
1827 DEBUG ("Page added (back to the same window)");
1828 self->priv->dnd_same_window = FALSE;
1832 DEBUG ("Page added");
1834 /* Get chat object */
1835 chat = EMPATHY_CHAT (child);
1837 /* Connect chat signals for this window */
1838 g_signal_connect (chat, "composing",
1839 G_CALLBACK (chat_window_composing_cb), self);
1840 g_signal_connect (chat, "new-message",
1841 G_CALLBACK (chat_window_new_message_cb), self);
1842 g_signal_connect (chat, "part-command-entered",
1843 G_CALLBACK (chat_window_command_part), NULL);
1844 g_signal_connect (chat, "notify::tp-chat",
1845 G_CALLBACK (chat_window_update_chat_tab), self);
1847 /* Set flag so we know to perform some special operations on
1848 * switch page due to the new page being added.
1850 self->priv->page_added = TRUE;
1852 /* Get list of chats up to date */
1853 self->priv->chats = g_list_append (self->priv->chats, chat);
1855 chat_window_update_chat_tab (chat);
1859 chat_window_page_removed_cb (GtkNotebook *notebook,
1862 EmpathyChatWindow *self)
1866 /* If we just received DND to the same window, we don't want
1867 * to do anything here like removing the tab and then readding
1868 * it, so we return here and in "page-added".
1870 if (self->priv->dnd_same_window)
1872 DEBUG ("Page removed (and will be readded to same window)");
1876 DEBUG ("Page removed");
1878 /* Get chat object */
1879 chat = EMPATHY_CHAT (child);
1881 /* Disconnect all signal handlers for this chat and this window */
1882 g_signal_handlers_disconnect_by_func (chat,
1883 G_CALLBACK (chat_window_composing_cb), self);
1884 g_signal_handlers_disconnect_by_func (chat,
1885 G_CALLBACK (chat_window_new_message_cb), self);
1886 g_signal_handlers_disconnect_by_func (chat,
1887 G_CALLBACK (chat_window_update_chat_tab), self);
1889 /* Keep list of chats up to date */
1890 self->priv->chats = g_list_remove (self->priv->chats, chat);
1891 empathy_chat_messages_read (chat);
1893 if (self->priv->chats == NULL)
1895 gtk_widget_destroy (GTK_WIDGET (self));
1899 chat_window_update (self, TRUE);
1904 chat_window_focus_in_event_cb (GtkWidget *widget,
1906 EmpathyChatWindow *self)
1908 empathy_chat_messages_read (self->priv->current_chat);
1910 chat_window_set_urgency_hint (self, FALSE);
1912 /* Update the title, since we now mark all unread messages as read. */
1913 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1919 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1920 EmpathyChatWindow *self)
1922 chat_window_contact_menu_update (self);
1926 chat_window_focus_out_event_cb (GtkWidget *widget,
1928 EmpathyChatWindow *self)
1930 if (self->priv->individual_mgr != NULL)
1933 /* Keep the individual manager alive so we won't fetch everything from Folks
1934 * each time we need to use it. Loading FolksAggregator can takes quite a
1935 * while (if user has a huge LDAP abook for example) and it blocks
1936 * the mainloop during most of this loading. We workaround this by loading
1937 * it when the chat window has been unfocused and so, hopefully, not impact
1938 * the reactivity of the chat window too much.
1940 * The individual manager (and so Folks) is needed to know to which
1941 * FolksIndividual a TpContact belongs, including:
1942 * - empathy_chat_get_contact_menu: to list all the personas of the contact
1943 * - empathy_display_individual_info: to invoke gnome-contacts with the
1944 * FolksIndividual.id of the contact
1945 * - drag_data_received_individual_id: to find the individual associated
1946 * with the ID we received from the DnD in order to invite him.
1948 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
1950 if (!empathy_individual_manager_get_contacts_loaded (
1951 self->priv->individual_mgr))
1953 /* We want to update the contact menu when Folks is loaded so we can
1954 * list all the personas of the contact. */
1955 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
1956 G_CALLBACK (contacts_loaded_cb), self, 0);
1959 g_object_notify (G_OBJECT (self), "individual-manager");
1965 chat_window_drag_drop (GtkWidget *widget,
1966 GdkDragContext *context,
1970 EmpathyChatWindow *self)
1974 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1975 if (target == GDK_NONE)
1976 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1978 if (target != GDK_NONE)
1980 gtk_drag_get_data (widget, context, target, time_);
1988 chat_window_drag_motion (GtkWidget *widget,
1989 GdkDragContext *context,
1993 EmpathyChatWindow *self)
1997 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1999 if (target != GDK_NONE)
2001 /* This is a file drag. Ensure the contact is online and set the
2002 drag type to COPY. Note that it's possible that the tab will
2003 be switched by GTK+ after a timeout from drag_motion without
2004 getting another drag_motion to disable the drop. You have
2005 to hold your mouse really still.
2007 EmpathyContact *contact;
2009 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2011 /* contact is NULL for multi-user chats. We don't do
2012 * file transfers to MUCs. We also don't send files
2013 * to offline contacts or contacts that don't support
2016 if ((contact == NULL) || !empathy_contact_is_online (contact))
2018 gdk_drag_status (context, 0, time_);
2022 if (!(empathy_contact_get_capabilities (contact)
2023 & EMPATHY_CAPABILITIES_FT))
2025 gdk_drag_status (context, 0, time_);
2029 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2033 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2034 if (target != GDK_NONE)
2036 /* This is a drag of a contact from a contact list. Set to COPY.
2037 FIXME: If this drag is to a MUC window, it invites the user.
2038 Otherwise, it opens a chat. Should we use a different drag
2039 type for invites? Should we allow ASK?
2041 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2049 drag_data_received_individual_id (EmpathyChatWindow *self,
2051 GdkDragContext *context,
2054 GtkSelectionData *selection,
2059 FolksIndividual *individual;
2060 EmpathyTpChat *chat;
2061 TpContact *tp_contact;
2063 EmpathyContact *contact;
2065 id = (const gchar *) gtk_selection_data_get_data (selection);
2067 DEBUG ("DND invididual %s", id);
2069 if (self->priv->current_chat == NULL)
2072 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2076 if (!empathy_tp_chat_can_add_contact (chat))
2078 DEBUG ("Can't invite contact to %s",
2079 tp_proxy_get_object_path (chat));
2083 if (self->priv->individual_mgr == NULL)
2084 /* Not likely as we have to focus out the chat window in order to start
2085 * the DnD but best to be safe. */
2088 individual = empathy_individual_manager_lookup_member (
2089 self->priv->individual_mgr, id);
2090 if (individual == NULL)
2092 DEBUG ("Failed to find individual %s", id);
2096 conn = tp_channel_get_connection ((TpChannel *) chat);
2097 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2098 if (tp_contact == NULL)
2100 DEBUG ("Can't find a TpContact on connection %s for %s",
2101 tp_proxy_get_object_path (conn), id);
2105 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2106 tp_channel_get_identifier ((TpChannel *) chat));
2108 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2109 empathy_tp_chat_add (chat, contact, NULL);
2110 g_object_unref (contact);
2113 gtk_drag_finish (context, TRUE, FALSE, time_);
2117 chat_window_drag_data_received (GtkWidget *widget,
2118 GdkDragContext *context,
2121 GtkSelectionData *selection,
2124 EmpathyChatWindow *self)
2126 if (info == DND_DRAG_TYPE_CONTACT_ID)
2128 EmpathyChat *chat = NULL;
2129 EmpathyChatWindow *old_window;
2130 TpAccount *account = NULL;
2131 EmpathyClientFactory *factory;
2134 const gchar *account_id;
2135 const gchar *contact_id;
2137 id = (const gchar*) gtk_selection_data_get_data (selection);
2139 factory = empathy_client_factory_dup ();
2141 DEBUG ("DND contact from roster with id:'%s'", id);
2143 strv = g_strsplit (id, ":", 2);
2144 if (g_strv_length (strv) == 2)
2146 account_id = strv[0];
2147 contact_id = strv[1];
2149 account = tp_simple_client_factory_ensure_account (
2150 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2152 g_object_unref (factory);
2153 if (account != NULL)
2154 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2157 if (account == NULL)
2160 gtk_drag_finish (context, FALSE, FALSE, time_);
2166 empathy_chat_with_contact_id (account, contact_id,
2167 empathy_get_current_action_time (), NULL, NULL);
2175 old_window = chat_window_find_chat (chat);
2178 if (old_window == self)
2180 gtk_drag_finish (context, TRUE, FALSE, time_);
2184 empathy_chat_window_move_chat (old_window, self, chat);
2188 empathy_chat_window_add_chat (self, chat);
2191 /* Added to take care of any outstanding chat events */
2192 empathy_chat_window_present_chat (chat,
2193 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2195 /* We should return TRUE to remove the data when doing
2196 * GDK_ACTION_MOVE, but we don't here otherwise it has
2197 * weird consequences, and we handle that internally
2198 * anyway with add_chat () and remove_chat ().
2200 gtk_drag_finish (context, TRUE, FALSE, time_);
2202 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2204 drag_data_received_individual_id (self, widget, context, x, y,
2205 selection, info, time_);
2207 else if (info == DND_DRAG_TYPE_URI_LIST)
2209 EmpathyContact *contact;
2212 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2214 /* contact is NULL when current_chat is a multi-user chat.
2215 * We don't do file transfers to MUCs, so just cancel the drag.
2217 if (contact == NULL)
2219 gtk_drag_finish (context, TRUE, FALSE, time_);
2223 data = (const gchar *) gtk_selection_data_get_data (selection);
2224 empathy_send_file_from_uri_list (contact, data);
2226 gtk_drag_finish (context, TRUE, FALSE, time_);
2228 else if (info == DND_DRAG_TYPE_TAB)
2231 EmpathyChatWindow *old_window = NULL;
2235 chat = (void *) gtk_selection_data_get_data (selection);
2236 old_window = chat_window_find_chat (*chat);
2240 self->priv->dnd_same_window = (old_window == self);
2242 DEBUG ("DND tab (within same window: %s)",
2243 self->priv->dnd_same_window ? "Yes" : "No");
2248 DEBUG ("DND from unknown source");
2249 gtk_drag_finish (context, FALSE, FALSE, time_);
2254 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2255 guint num_chats_in_manager,
2256 EmpathyChatWindow *self)
2258 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2259 num_chats_in_manager > 0);
2263 chat_window_finalize (GObject *object)
2265 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2267 DEBUG ("Finalized: %p", object);
2269 g_object_unref (self->priv->ui_manager);
2270 g_object_unref (self->priv->chatroom_manager);
2271 g_object_unref (self->priv->notify_mgr);
2272 g_object_unref (self->priv->gsettings_chat);
2273 g_object_unref (self->priv->gsettings_notif);
2274 g_object_unref (self->priv->gsettings_ui);
2275 g_object_unref (self->priv->sound_mgr);
2276 g_clear_object (&self->priv->individual_mgr);
2278 if (self->priv->notification != NULL)
2280 notify_notification_close (self->priv->notification, NULL);
2281 self->priv->notification = NULL;
2284 if (self->priv->contact_targets)
2285 gtk_target_list_unref (self->priv->contact_targets);
2287 if (self->priv->file_targets)
2288 gtk_target_list_unref (self->priv->file_targets);
2290 if (self->priv->chat_manager)
2292 g_signal_handler_disconnect (self->priv->chat_manager,
2293 self->priv->chat_manager_chats_changed_id);
2294 g_object_unref (self->priv->chat_manager);
2295 self->priv->chat_manager = NULL;
2298 chat_windows = g_list_remove (chat_windows, self);
2300 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2304 chat_window_get_property (GObject *object,
2309 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2311 switch (property_id)
2313 case PROP_INDIVIDUAL_MGR:
2314 g_value_set_object (value, self->priv->individual_mgr);
2316 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2322 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2324 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2327 object_class->get_property = chat_window_get_property;
2328 object_class->finalize = chat_window_finalize;
2330 spec = g_param_spec_object ("individual-manager", "individual-manager",
2331 "EmpathyIndividualManager",
2332 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2333 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2334 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2336 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2340 empathy_chat_window_init (EmpathyChatWindow *self)
2343 GtkAccelGroup *accel_group;
2348 GtkWidget *chat_vbox;
2350 EmpathySmileyManager *smiley_manager;
2352 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2353 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2355 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2356 gui = empathy_builder_get_file (filename,
2357 "chat_vbox", &chat_vbox,
2358 "ui_manager", &self->priv->ui_manager,
2359 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2360 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2361 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2362 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2363 "menu_edit_cut", &self->priv->menu_edit_cut,
2364 "menu_edit_copy", &self->priv->menu_edit_copy,
2365 "menu_edit_paste", &self->priv->menu_edit_paste,
2366 "menu_edit_find", &self->priv->menu_edit_find,
2367 "menu_tabs_next", &self->priv->menu_tabs_next,
2368 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2369 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2370 "menu_tabs_left", &self->priv->menu_tabs_left,
2371 "menu_tabs_right", &self->priv->menu_tabs_right,
2372 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2376 empathy_builder_connect (gui, self,
2377 "menu_conv", "activate", chat_window_conv_activate_cb,
2378 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2379 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2380 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2381 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2382 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2383 "menu_conv_close", "activate", chat_window_close_activate_cb,
2384 "menu_edit", "activate", chat_window_edit_activate_cb,
2385 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2386 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2387 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2388 "menu_edit_find", "activate", chat_window_find_activate_cb,
2389 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2390 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2391 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2392 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2393 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2394 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2395 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2396 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2399 empathy_set_css_provider (GTK_WIDGET (self));
2401 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2402 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2403 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2404 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2406 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2408 self->priv->notebook = gtk_notebook_new ();
2410 g_signal_connect (self->priv->notebook, "create-window",
2411 G_CALLBACK (notebook_create_window_cb), self);
2413 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2415 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2416 "EmpathyChatWindow");
2417 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2418 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2419 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2420 gtk_widget_show (self->priv->notebook);
2423 accel_group = gtk_accel_group_new ();
2424 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2426 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2428 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2431 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2435 g_object_unref (accel_group);
2437 /* Set up drag target lists */
2438 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2439 G_N_ELEMENTS (drag_types_dest_contact));
2441 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2442 G_N_ELEMENTS (drag_types_dest_file));
2444 /* Set up smiley menu */
2445 smiley_manager = empathy_smiley_manager_dup_singleton ();
2446 submenu = empathy_smiley_menu_new (smiley_manager,
2447 chat_window_insert_smiley_activate_cb, self);
2449 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2450 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2451 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2452 g_object_unref (smiley_manager);
2454 /* Set up signals we can't do with ui file since we may need to
2455 * block/unblock them at some later stage.
2458 g_signal_connect (self, "delete_event",
2459 G_CALLBACK (chat_window_delete_event_cb), self);
2460 g_signal_connect (self, "focus_in_event",
2461 G_CALLBACK (chat_window_focus_in_event_cb), self);
2462 g_signal_connect (self, "focus_out_event",
2463 G_CALLBACK (chat_window_focus_out_event_cb), self);
2464 g_signal_connect_after (self->priv->notebook, "switch_page",
2465 G_CALLBACK (chat_window_page_switched_cb), self);
2466 g_signal_connect (self->priv->notebook, "page_added",
2467 G_CALLBACK (chat_window_page_added_cb), self);
2468 g_signal_connect (self->priv->notebook, "page_removed",
2469 G_CALLBACK (chat_window_page_removed_cb), self);
2471 /* Set up drag and drop */
2472 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2473 GTK_DEST_DEFAULT_HIGHLIGHT,
2475 G_N_ELEMENTS (drag_types_dest),
2476 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2478 /* connect_after to allow GtkNotebook's built-in tab switching */
2479 g_signal_connect_after (self->priv->notebook, "drag-motion",
2480 G_CALLBACK (chat_window_drag_motion), self);
2481 g_signal_connect (self->priv->notebook, "drag-data-received",
2482 G_CALLBACK (chat_window_drag_data_received), self);
2483 g_signal_connect (self->priv->notebook, "drag-drop",
2484 G_CALLBACK (chat_window_drag_drop), self);
2486 chat_windows = g_list_prepend (chat_windows, self);
2488 /* Set up private details */
2489 self->priv->chats = NULL;
2490 self->priv->current_chat = NULL;
2491 self->priv->notification = NULL;
2493 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2495 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2496 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2497 self->priv->chat_manager, "closed-chats-changed",
2498 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2500 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2501 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2503 g_object_ref (self->priv->ui_manager);
2504 g_object_unref (gui);
2507 /* Returns the window to open a new tab in if there is a suitable window,
2508 * otherwise, returns NULL indicating that a new window should be added.
2510 static EmpathyChatWindow *
2511 empathy_chat_window_get_default (gboolean room)
2513 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2515 gboolean separate_windows = TRUE;
2517 separate_windows = g_settings_get_boolean (gsettings,
2518 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2520 g_object_unref (gsettings);
2522 if (separate_windows)
2523 /* Always create a new window */
2526 for (l = chat_windows; l; l = l->next)
2528 EmpathyChatWindow *chat_window;
2529 guint nb_rooms, nb_private;
2531 chat_window = l->data;
2533 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2535 /* Skip the window if there aren't any rooms in it */
2536 if (room && nb_rooms == 0)
2539 /* Skip the window if there aren't any 1-1 chats in it */
2540 if (!room && nb_private == 0)
2550 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2554 GtkWidget *popup_label;
2556 GValue value = { 0, };
2558 g_return_if_fail (self != NULL);
2559 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2561 /* Reference the chat object */
2562 g_object_ref (chat);
2564 /* If this window has just been created, position it */
2565 if (self->priv->chats == NULL)
2567 const gchar *name = "chat-window";
2568 gboolean separate_windows;
2570 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2571 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2573 if (empathy_chat_is_room (chat))
2574 name = "room-window";
2576 if (separate_windows)
2580 /* Save current position of the window */
2581 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2583 /* First bind to the 'generic' name. So new window for which we didn't
2584 * save a geometry yet will have the geometry of the last saved
2585 * window (bgo #601191). */
2586 empathy_geometry_bind (GTK_WINDOW (self), name);
2588 /* Restore previous position of the window so the newly created window
2589 * won't be in the same position as the latest saved window and so
2590 * completely hide it. */
2591 gtk_window_move (GTK_WINDOW (self), x, y);
2593 /* Then bind it to the name of the contact/room so we'll save the
2594 * geometry specific to this window */
2595 name = empathy_chat_get_id (chat);
2598 empathy_geometry_bind (GTK_WINDOW (self), name);
2601 child = GTK_WIDGET (chat);
2602 label = chat_window_create_label (self, chat, TRUE);
2603 popup_label = chat_window_create_label (self, chat, FALSE);
2604 gtk_widget_show (child);
2606 g_signal_connect (chat, "notify::name",
2607 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2608 g_signal_connect (chat, "notify::subject",
2609 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2610 g_signal_connect (chat, "notify::remote-contact",
2611 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2612 g_signal_connect (chat, "notify::sms-channel",
2613 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2614 g_signal_connect (chat, "notify::n-messages-sending",
2615 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2616 g_signal_connect (chat, "notify::nb-unread-messages",
2617 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2618 chat_window_chat_notify_cb (chat);
2620 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2622 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2623 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2624 g_value_init (&value, G_TYPE_BOOLEAN);
2625 g_value_set_boolean (&value, TRUE);
2626 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2627 child, "tab-expand" , &value);
2628 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2629 child, "tab-fill" , &value);
2630 g_value_unset (&value);
2632 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2636 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2640 EmpathyContact *remote_contact;
2641 EmpathyChatManager *chat_manager;
2643 g_return_if_fail (self != NULL);
2644 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2646 g_signal_handlers_disconnect_by_func (chat,
2647 chat_window_chat_notify_cb, NULL);
2649 remote_contact = g_object_get_data (G_OBJECT (chat),
2650 "chat-window-remote-contact");
2654 g_signal_handlers_disconnect_by_func (remote_contact,
2655 chat_window_update_chat_tab, chat);
2658 chat_manager = empathy_chat_manager_dup_singleton ();
2659 empathy_chat_manager_closed_chat (chat_manager, chat);
2660 g_object_unref (chat_manager);
2662 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2664 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2666 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2668 g_object_unref (chat);
2672 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2673 EmpathyChatWindow *new_window,
2678 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2679 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2680 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2682 widget = GTK_WIDGET (chat);
2684 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2685 G_OBJECT (widget)->ref_count);
2687 /* We reference here to make sure we don't loose the widget
2688 * and the EmpathyChat object during the move.
2690 g_object_ref (chat);
2691 g_object_ref (widget);
2693 empathy_chat_window_remove_chat (old_window, chat);
2694 empathy_chat_window_add_chat (new_window, chat);
2696 g_object_unref (widget);
2697 g_object_unref (chat);
2701 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2706 g_return_if_fail (self != NULL);
2707 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2709 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2712 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2717 empathy_chat_window_find_chat (TpAccount *account,
2719 gboolean sms_channel)
2723 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2725 for (l = chat_windows; l; l = l->next)
2727 EmpathyChatWindow *window = l->data;
2730 for (ll = window->priv->chats; ll; ll = ll->next)
2736 if (account == empathy_chat_get_account (chat) &&
2737 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2738 sms_channel == empathy_chat_is_sms_channel (chat))
2747 empathy_chat_window_present_chat (EmpathyChat *chat,
2750 EmpathyChatWindow *self;
2751 guint32 x_timestamp;
2753 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2755 self = chat_window_find_chat (chat);
2757 /* If the chat has no window, create one */
2760 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2763 self = empathy_chat_window_new ();
2765 /* we want to display the newly created window even if we
2766 * don't present it */
2767 gtk_widget_show (GTK_WIDGET (self));
2770 empathy_chat_window_add_chat (self, chat);
2773 /* Don't force the window to show itself when it wasn't
2774 * an action by the user
2776 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2779 if (x_timestamp != GDK_CURRENT_TIME)
2781 /* Don't present or switch tab if the action was earlier than the
2782 * last actions X time, accounting for overflow and the first ever
2785 if (self->priv->x_user_action_time != 0
2786 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2789 self->priv->x_user_action_time = x_timestamp;
2792 empathy_chat_window_switch_to_chat (self, chat);
2794 /* Don't use empathy_window_present_with_time () which would move the window
2795 * to our current desktop but move to the window's desktop instead. This is
2796 * more coherent with Shell's 'app is ready' notication which moves the view
2797 * to the app desktop rather than moving the app itself. */
2798 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2800 gtk_widget_grab_focus (chat->input_text_view);
2805 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2810 guint _nb_rooms = 0, _nb_private = 0;
2812 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2814 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2820 if (nb_rooms != NULL)
2821 *nb_rooms = _nb_rooms;
2822 if (nb_private != NULL)
2823 *nb_private = _nb_private;
2826 EmpathyIndividualManager *
2827 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2829 return self->priv->individual_mgr;