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 remove_all_chats (EmpathyChatWindow *window)
218 EmpathyChatWindowPriv *priv;
220 priv = GET_PRIV (window);
221 g_object_ref (window);
223 while (priv->chats) {
224 empathy_chat_window_remove_chat (window, priv->chats->data);
227 g_object_unref (window);
231 confirm_close_response_cb (GtkWidget *dialog,
233 EmpathyChatWindow *window)
237 chat = g_object_get_data (G_OBJECT (dialog), "chat");
239 gtk_widget_destroy (dialog);
241 if (response != GTK_RESPONSE_ACCEPT)
245 empathy_chat_window_remove_chat (window, chat);
247 remove_all_chats (window);
252 confirm_close (EmpathyChatWindow *window,
253 gboolean close_window,
257 EmpathyChatWindowPriv *priv;
259 gchar *primary, *secondary;
261 g_return_if_fail (n_rooms > 0);
264 g_return_if_fail (chat == NULL);
266 g_return_if_fail (chat != NULL);
269 priv = GET_PRIV (window);
271 /* If there are no chats in this window, how could we possibly have got
274 g_return_if_fail (priv->chats != NULL);
276 /* Treat closing a window which only has one tab exactly like closing
279 if (close_window && priv->chats->next == NULL) {
280 close_window = FALSE;
281 chat = priv->chats->data;
285 primary = g_strdup (_("Close this window?"));
288 gchar *chat_name = empathy_chat_dup_name (chat);
289 secondary = g_strdup_printf (
290 _("Closing this window will leave %s. You will "
291 "not receive any further messages until you "
296 secondary = g_strdup_printf (
297 /* Note to translators: the number of chats will
298 * always be at least 2.
301 "Closing this window will leave a chat room. You will "
302 "not receive any further messages until you rejoin it.",
303 "Closing this window will leave %u chat rooms. You will "
304 "not receive any further messages until you rejoin them.",
309 gchar *chat_name = empathy_chat_dup_name (chat);
310 primary = g_strdup_printf (_("Leave %s?"), chat_name);
311 secondary = g_strdup (_("You will not receive any further messages from this chat "
312 "room until you rejoin it."));
316 dialog = gtk_message_dialog_new (
317 GTK_WINDOW (priv->dialog),
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);
339 g_signal_connect (dialog, "response",
340 G_CALLBACK (confirm_close_response_cb), window);
342 gtk_window_present (GTK_WINDOW (dialog));
346 maybe_close_chat (EmpathyChatWindow *window,
349 g_return_if_fail (chat != NULL);
351 if (empathy_chat_is_room (chat)) {
352 confirm_close (window, FALSE, 1, chat);
354 empathy_chat_window_remove_chat (window, chat);
359 chat_window_close_clicked_cb (GtkAction *action,
362 EmpathyChatWindow *window;
364 window = chat_window_find_chat (chat);
365 maybe_close_chat (window, chat);
369 chat_tab_style_updated_cb (GtkWidget *hbox,
373 int char_width, h, w;
374 PangoContext *context;
375 const PangoFontDescription *font_desc;
376 PangoFontMetrics *metrics;
378 button = g_object_get_data (G_OBJECT (user_data),
379 "chat-window-tab-close-button");
380 context = gtk_widget_get_pango_context (hbox);
382 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
383 GTK_STATE_FLAG_NORMAL);
385 metrics = pango_context_get_metrics (context, font_desc,
386 pango_context_get_language (context));
387 char_width = pango_font_metrics_get_approximate_char_width (metrics);
388 pango_font_metrics_unref (metrics);
390 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
391 GTK_ICON_SIZE_MENU, &w, &h);
393 /* Request at least about 12 chars width plus at least space for the status
394 * image and the close button */
395 gtk_widget_set_size_request (hbox,
396 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
398 gtk_widget_set_size_request (button, w, h);
402 chat_window_create_label (EmpathyChatWindow *window,
404 gboolean is_tab_label)
407 GtkWidget *name_label;
408 GtkWidget *status_image;
409 GtkWidget *event_box;
410 GtkWidget *event_box_hbox;
411 PangoAttrList *attr_list;
412 PangoAttribute *attr;
414 /* The spacing between the button and the label. */
415 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
417 event_box = gtk_event_box_new ();
418 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
420 name_label = gtk_label_new (NULL);
422 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
424 attr_list = pango_attr_list_new ();
425 attr = pango_attr_scale_new (1/1.2);
426 attr->start_index = 0;
427 attr->end_index = -1;
428 pango_attr_list_insert (attr_list, attr);
429 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
430 pango_attr_list_unref (attr_list);
432 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
433 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
434 g_object_set_data (G_OBJECT (chat),
435 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
438 status_image = gtk_image_new ();
440 /* Spacing between the icon and label. */
441 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
443 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
444 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
446 g_object_set_data (G_OBJECT (chat),
447 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
449 g_object_set_data (G_OBJECT (chat),
450 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
453 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
454 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
457 GtkWidget *close_button;
458 GtkWidget *sending_spinner;
460 sending_spinner = gtk_spinner_new ();
462 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
464 g_object_set_data (G_OBJECT (chat),
465 "chat-window-tab-sending-spinner",
468 close_button = gedit_close_button_new ();
469 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
471 /* We don't want focus/keynav for the button to avoid clutter, and
472 * Ctrl-W works anyway.
474 gtk_widget_set_can_focus (close_button, FALSE);
475 gtk_widget_set_can_default (close_button, FALSE);
477 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
479 g_signal_connect (close_button,
481 G_CALLBACK (chat_window_close_clicked_cb),
484 /* React to theme changes and also setup the size correctly. */
485 g_signal_connect (hbox,
487 G_CALLBACK (chat_tab_style_updated_cb),
491 gtk_widget_show_all (hbox);
497 _submenu_notify_visible_changed_cb (GObject *object,
501 g_signal_handlers_disconnect_by_func (object,
502 _submenu_notify_visible_changed_cb,
504 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
508 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
513 gboolean wrap_around;
514 gboolean is_connected;
517 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
518 first_page = (page_num == 0);
519 last_page = (page_num == (num_pages - 1));
520 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
522 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
524 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
526 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
528 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
529 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
530 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
531 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
535 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
536 EmpathyChatWindow *self)
538 EmpathyTpChat *tp_chat;
539 TpConnection *connection;
541 gboolean sensitive = FALSE;
543 g_return_if_fail (priv->current_chat != NULL);
545 action = gtk_ui_manager_get_action (priv->ui_manager,
546 "/chats_menubar/menu_conv/menu_conv_invite_participant");
547 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
549 if (tp_chat != NULL) {
550 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
552 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
553 (tp_connection_get_status (connection, NULL) ==
554 TP_CONNECTION_STATUS_CONNECTED);
557 gtk_action_set_sensitive (action, sensitive);
561 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
562 EmpathyChatWindow *window)
564 GtkWidget *menu, *submenu, *orig_submenu;
566 menu = gtk_ui_manager_get_widget (priv->ui_manager,
567 "/chats_menubar/menu_contact");
568 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
570 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
571 submenu = empathy_chat_get_contact_menu (priv->current_chat);
573 if (submenu != NULL) {
574 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
575 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
577 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
578 gtk_widget_show (menu);
579 gtk_widget_set_sensitive (menu, TRUE);
581 gtk_widget_set_sensitive (menu, FALSE);
584 tp_g_signal_connect_object (orig_submenu,
586 (GCallback)_submenu_notify_visible_changed_cb,
592 get_all_unread_messages (EmpathyChatWindowPriv *priv)
597 for (l = priv->chats; l != NULL; l = g_list_next (l))
598 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
604 get_window_title_name (EmpathyChatWindowPriv *priv)
606 gchar *active_name, *ret;
608 guint current_unread_msgs;
610 nb_chats = g_list_length (priv->chats);
611 g_assert (nb_chats > 0);
613 active_name = empathy_chat_dup_name (priv->current_chat);
615 current_unread_msgs = empathy_chat_get_nb_unread_messages (
620 if (current_unread_msgs == 0)
621 ret = g_strdup (active_name);
623 ret = g_strdup_printf (ngettext (
625 "%s (%d unread)", current_unread_msgs),
626 active_name, current_unread_msgs);
628 guint nb_others = nb_chats - 1;
629 guint all_unread_msgs;
631 all_unread_msgs = get_all_unread_messages (priv);
633 if (all_unread_msgs == 0) {
634 /* no unread message */
635 ret = g_strdup_printf (ngettext (
637 "%s (and %u others)", nb_others),
638 active_name, nb_others);
641 else if (all_unread_msgs == current_unread_msgs) {
642 /* unread messages are in the current tab */
643 ret = g_strdup_printf (ngettext (
645 "%s (%d unread)", current_unread_msgs),
646 active_name, current_unread_msgs);
649 else if (current_unread_msgs == 0) {
650 /* unread messages are in other tabs */
651 ret = g_strdup_printf (ngettext (
652 "%s (%d unread from others)",
653 "%s (%d unread from others)",
655 active_name, all_unread_msgs);
659 /* unread messages are in all the tabs */
660 ret = g_strdup_printf (ngettext (
661 "%s (%d unread from all)",
662 "%s (%d unread from all)",
664 active_name, all_unread_msgs);
668 g_free (active_name);
674 chat_window_title_update (EmpathyChatWindowPriv *priv)
678 name = get_window_title_name (priv);
679 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
684 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
687 EmpathyContact *remote_contact;
688 gboolean avatar_in_icon;
691 n_chats = g_list_length (priv->chats);
693 /* Update window icon */
695 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
696 EMPATHY_IMAGE_MESSAGE);
698 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
699 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
701 if (n_chats == 1 && avatar_in_icon) {
702 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
703 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
704 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
707 g_object_unref (icon);
710 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
716 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
720 GtkWidget *chat_close_button;
723 if (num_pages == 1) {
724 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
725 chat_close_button = g_object_get_data (G_OBJECT (chat),
726 "chat-window-tab-close-button");
727 gtk_widget_hide (chat_close_button);
729 for (i=0; i<num_pages; i++) {
730 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
731 chat_close_button = g_object_get_data (G_OBJECT (chat),
732 "chat-window-tab-close-button");
733 gtk_widget_show (chat_close_button);
739 chat_window_update (EmpathyChatWindow *window,
740 gboolean update_contact_menu)
742 EmpathyChatWindowPriv *priv = GET_PRIV (window);
745 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
747 /* Update Tab menu */
748 chat_window_menu_context_update (priv,
751 chat_window_conversation_menu_update (priv, window);
753 /* If this update is due to a focus-in event, we know the menu will be
754 the same as when we last left it, so no work to do. Besides, if we
755 swap out the menu on a focus-in, we may confuse any external global
757 if (update_contact_menu) {
758 chat_window_contact_menu_update (priv,
762 chat_window_title_update (priv);
764 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
766 chat_window_close_button_update (priv,
771 append_markup_printf (GString *string,
778 va_start (args, format);
780 tmp = g_markup_vprintf_escaped (format, args);
781 g_string_append (string, tmp);
788 chat_window_update_chat_tab_full (EmpathyChat *chat,
789 gboolean update_contact_menu)
791 EmpathyChatWindow *window;
792 EmpathyChatWindowPriv *priv;
793 EmpathyContact *remote_contact;
797 const gchar *subject;
798 const gchar *status = NULL;
802 const gchar *icon_name;
803 GtkWidget *tab_image;
804 GtkWidget *menu_image;
805 GtkWidget *sending_spinner;
808 window = chat_window_find_chat (chat);
812 priv = GET_PRIV (window);
814 /* Get information */
815 name = empathy_chat_dup_name (chat);
816 account = empathy_chat_get_account (chat);
817 subject = empathy_chat_get_subject (chat);
818 remote_contact = empathy_chat_get_remote_contact (chat);
820 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
821 name, tp_proxy_get_object_path (account), subject, remote_contact);
823 /* Update tab image */
824 if (empathy_chat_get_tp_chat (chat) == NULL) {
825 /* No TpChat, we are disconnected */
828 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
829 icon_name = EMPATHY_IMAGE_MESSAGE;
831 else if (remote_contact && empathy_chat_is_composing (chat)) {
832 icon_name = EMPATHY_IMAGE_TYPING;
834 else if (empathy_chat_is_sms_channel (chat)) {
835 icon_name = EMPATHY_IMAGE_SMS;
837 else if (remote_contact) {
838 icon_name = empathy_icon_name_for_contact (remote_contact);
840 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
843 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
844 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
845 if (icon_name != NULL) {
846 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
847 gtk_widget_show (tab_image);
848 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
849 gtk_widget_show (menu_image);
851 gtk_widget_hide (tab_image);
852 gtk_widget_hide (menu_image);
855 /* Update the sending spinner */
856 nb_sending = empathy_chat_get_n_messages_sending (chat);
857 sending_spinner = g_object_get_data (G_OBJECT (chat),
858 "chat-window-tab-sending-spinner");
860 g_object_set (sending_spinner,
861 "active", nb_sending > 0,
862 "visible", nb_sending > 0,
865 /* Update tab tooltip */
866 tooltip = g_string_new (NULL);
868 if (remote_contact) {
869 id = empathy_contact_get_id (remote_contact);
870 status = empathy_contact_get_presence_message (remote_contact);
875 if (empathy_chat_is_sms_channel (chat)) {
876 append_markup_printf (tooltip, "%s ", _("SMS:"));
879 append_markup_printf (tooltip,
880 "<b>%s</b><small> (%s)</small>",
882 tp_account_get_display_name (account));
884 if (nb_sending > 0) {
885 char *tmp = g_strdup_printf (
886 ngettext ("Sending %d message",
887 "Sending %d messages",
891 g_string_append (tooltip, "\n");
892 g_string_append (tooltip, tmp);
894 gtk_widget_set_tooltip_text (sending_spinner, tmp);
898 if (!EMP_STR_EMPTY (status)) {
899 append_markup_printf (tooltip, "\n<i>%s</i>", status);
903 append_markup_printf (tooltip, "\n<b>%s</b> %s",
904 _("Topic:"), subject);
907 if (remote_contact && empathy_chat_is_composing (chat)) {
908 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
911 markup = g_string_free (tooltip, FALSE);
912 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
913 gtk_widget_set_tooltip_markup (widget, markup);
914 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
915 gtk_widget_set_tooltip_markup (widget, markup);
918 /* Update tab and menu label */
919 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
920 gtk_label_set_text (GTK_LABEL (widget), name);
921 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
922 gtk_label_set_text (GTK_LABEL (widget), name);
924 /* Update the window if it's the current chat */
925 if (priv->current_chat == chat) {
926 chat_window_update (window, update_contact_menu);
933 chat_window_update_chat_tab (EmpathyChat *chat)
935 chat_window_update_chat_tab_full (chat, TRUE);
939 chat_window_chat_notify_cb (EmpathyChat *chat)
941 EmpathyChatWindow *window;
942 EmpathyContact *old_remote_contact;
943 EmpathyContact *remote_contact = NULL;
945 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
946 remote_contact = empathy_chat_get_remote_contact (chat);
948 if (old_remote_contact != remote_contact) {
949 /* The remote-contact associated with the chat changed, we need
950 * to keep track of any change of that contact and update the
951 * window each time. */
952 if (remote_contact) {
953 g_signal_connect_swapped (remote_contact, "notify",
954 G_CALLBACK (chat_window_update_chat_tab),
957 if (old_remote_contact) {
958 g_signal_handlers_disconnect_by_func (old_remote_contact,
959 chat_window_update_chat_tab,
963 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
964 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
967 chat_window_update_chat_tab (chat);
969 window = chat_window_find_chat (chat);
970 if (window != NULL) {
971 chat_window_update (window, FALSE);
976 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
977 EmpathySmiley *smiley,
980 EmpathyChatWindowPriv *priv = GET_PRIV (window);
982 GtkTextBuffer *buffer;
985 chat = priv->current_chat;
987 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
988 gtk_text_buffer_get_end_iter (buffer, &iter);
989 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
993 chat_window_conv_activate_cb (GtkAction *action,
994 EmpathyChatWindow *window)
996 EmpathyChatWindowPriv *priv = GET_PRIV (window);
999 EmpathyContact *remote_contact = NULL;
1001 /* Favorite room menu */
1002 is_room = empathy_chat_is_room (priv->current_chat);
1006 gboolean found = FALSE;
1007 EmpathyChatroom *chatroom;
1009 room = empathy_chat_get_id (priv->current_chat);
1010 account = empathy_chat_get_account (priv->current_chat);
1011 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1013 if (chatroom != NULL)
1014 found = empathy_chatroom_is_favorite (chatroom);
1016 DEBUG ("This room %s favorite", found ? "is" : "is not");
1017 gtk_toggle_action_set_active (
1018 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1020 if (chatroom != NULL)
1021 found = empathy_chatroom_is_always_urgent (chatroom);
1023 gtk_toggle_action_set_active (
1024 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1027 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1028 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1030 /* Show contacts menu */
1031 g_object_get (priv->current_chat,
1032 "remote-contact", &remote_contact,
1033 "show-contacts", &active,
1035 if (remote_contact == NULL) {
1036 gtk_toggle_action_set_active (
1037 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1040 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1041 (remote_contact == NULL));
1042 if (remote_contact != NULL) {
1043 g_object_unref (remote_contact);
1048 chat_window_clear_activate_cb (GtkAction *action,
1049 EmpathyChatWindow *window)
1051 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1053 empathy_chat_clear (priv->current_chat);
1057 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1058 EmpathyChatWindow *window)
1060 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1065 EmpathyChatroom *chatroom;
1067 active = gtk_toggle_action_get_active (toggle_action);
1068 account = empathy_chat_get_account (priv->current_chat);
1069 room = empathy_chat_get_id (priv->current_chat);
1070 name = empathy_chat_dup_name (priv->current_chat);
1072 chatroom = empathy_chatroom_manager_ensure_chatroom (
1073 priv->chatroom_manager,
1078 empathy_chatroom_set_favorite (chatroom, active);
1079 g_object_unref (chatroom);
1084 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1085 EmpathyChatWindow *window)
1087 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1092 EmpathyChatroom *chatroom;
1094 active = gtk_toggle_action_get_active (toggle_action);
1095 account = empathy_chat_get_account (priv->current_chat);
1096 room = empathy_chat_get_id (priv->current_chat);
1097 name = empathy_chat_dup_name (priv->current_chat);
1099 chatroom = empathy_chatroom_manager_ensure_chatroom (
1100 priv->chatroom_manager,
1105 empathy_chatroom_set_always_urgent (chatroom, active);
1106 g_object_unref (chatroom);
1111 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1112 EmpathyChatWindow *window)
1114 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1117 active = gtk_toggle_action_get_active (toggle_action);
1119 empathy_chat_set_show_contacts (priv->current_chat, active);
1123 chat_window_invite_participant_activate_cb (GtkAction *action,
1124 EmpathyChatWindow *window)
1126 EmpathyChatWindowPriv *priv;
1128 EmpathyTpChat *tp_chat;
1131 priv = GET_PRIV (window);
1133 g_return_if_fail (priv->current_chat != NULL);
1135 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1137 dialog = empathy_invite_participant_dialog_new (
1138 GTK_WINDOW (priv->dialog), tp_chat);
1139 gtk_widget_show (dialog);
1141 response = gtk_dialog_run (GTK_DIALOG (dialog));
1143 if (response == GTK_RESPONSE_ACCEPT) {
1144 TpContact *tp_contact;
1145 EmpathyContact *contact;
1147 tp_contact = empathy_invite_participant_dialog_get_selected (
1148 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1149 if (tp_contact == NULL) goto out;
1151 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1153 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1154 contact, _("Inviting you to this room"));
1156 g_object_unref (contact);
1160 gtk_widget_destroy (dialog);
1164 chat_window_close_activate_cb (GtkAction *action,
1165 EmpathyChatWindow *window)
1167 EmpathyChatWindowPriv *priv;
1169 priv = GET_PRIV (window);
1171 g_return_if_fail (priv->current_chat != NULL);
1173 maybe_close_chat (window, priv->current_chat);
1177 chat_window_edit_activate_cb (GtkAction *action,
1178 EmpathyChatWindow *window)
1180 EmpathyChatWindowPriv *priv;
1181 GtkClipboard *clipboard;
1182 GtkTextBuffer *buffer;
1183 gboolean text_available;
1185 priv = GET_PRIV (window);
1187 g_return_if_fail (priv->current_chat != NULL);
1189 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1190 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1191 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1192 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1196 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1197 if (gtk_text_buffer_get_has_selection (buffer)) {
1198 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1199 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1203 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1205 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1206 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1209 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1210 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1211 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1215 chat_window_cut_activate_cb (GtkAction *action,
1216 EmpathyChatWindow *window)
1218 EmpathyChatWindowPriv *priv;
1220 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1222 priv = GET_PRIV (window);
1224 empathy_chat_cut (priv->current_chat);
1228 chat_window_copy_activate_cb (GtkAction *action,
1229 EmpathyChatWindow *window)
1231 EmpathyChatWindowPriv *priv;
1233 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1235 priv = GET_PRIV (window);
1237 empathy_chat_copy (priv->current_chat);
1241 chat_window_paste_activate_cb (GtkAction *action,
1242 EmpathyChatWindow *window)
1244 EmpathyChatWindowPriv *priv;
1246 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1248 priv = GET_PRIV (window);
1250 empathy_chat_paste (priv->current_chat);
1254 chat_window_find_activate_cb (GtkAction *action,
1255 EmpathyChatWindow *window)
1257 EmpathyChatWindowPriv *priv;
1259 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1261 priv = GET_PRIV (window);
1263 empathy_chat_find (priv->current_chat);
1267 chat_window_tabs_next_activate_cb (GtkAction *action,
1268 EmpathyChatWindow *window)
1270 EmpathyChatWindowPriv *priv;
1271 gint index_, numPages;
1272 gboolean wrap_around;
1274 priv = GET_PRIV (window);
1276 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1277 &wrap_around, NULL);
1279 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1280 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1282 if (index_ == (numPages - 1) && wrap_around) {
1283 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1287 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1291 chat_window_tabs_previous_activate_cb (GtkAction *action,
1292 EmpathyChatWindow *window)
1294 EmpathyChatWindowPriv *priv;
1295 gint index_, numPages;
1296 gboolean wrap_around;
1298 priv = GET_PRIV (window);
1300 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1301 &wrap_around, NULL);
1303 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1304 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1306 if (index_ <= 0 && wrap_around) {
1307 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1311 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1315 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1316 EmpathyChatWindow *window)
1318 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1319 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1320 empathy_get_current_action_time ());
1324 chat_window_tabs_left_activate_cb (GtkAction *action,
1325 EmpathyChatWindow *window)
1327 EmpathyChatWindowPriv *priv;
1329 gint index_, num_pages;
1331 priv = GET_PRIV (window);
1333 chat = priv->current_chat;
1334 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1339 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1343 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1344 chat_window_menu_context_update (priv, num_pages);
1348 chat_window_tabs_right_activate_cb (GtkAction *action,
1349 EmpathyChatWindow *window)
1351 EmpathyChatWindowPriv *priv;
1353 gint index_, num_pages;
1355 priv = GET_PRIV (window);
1357 chat = priv->current_chat;
1358 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1360 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1364 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1365 chat_window_menu_context_update (priv, num_pages);
1368 static EmpathyChatWindow *
1369 empathy_chat_window_new (void)
1371 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1375 chat_window_detach_activate_cb (GtkAction *action,
1376 EmpathyChatWindow *window)
1378 EmpathyChatWindowPriv *priv;
1379 EmpathyChatWindow *new_window;
1382 priv = GET_PRIV (window);
1384 chat = priv->current_chat;
1385 new_window = empathy_chat_window_new ();
1387 empathy_chat_window_move_chat (window, new_window, chat);
1389 priv = GET_PRIV (new_window);
1390 gtk_widget_show (priv->dialog);
1394 chat_window_help_contents_activate_cb (GtkAction *action,
1395 EmpathyChatWindow *window)
1397 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1399 empathy_url_show (priv->dialog, "ghelp:empathy");
1403 chat_window_help_about_activate_cb (GtkAction *action,
1404 EmpathyChatWindow *window)
1406 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1408 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1412 chat_window_delete_event_cb (GtkWidget *dialog,
1414 EmpathyChatWindow *window)
1416 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1417 EmpathyChat *chat = NULL;
1421 DEBUG ("Delete event received");
1423 for (l = priv->chats; l != NULL; l = l->next) {
1424 if (empathy_chat_is_room (l->data)) {
1431 confirm_close (window, TRUE, n_rooms,
1432 (n_rooms == 1 ? chat : NULL));
1434 remove_all_chats (window);
1441 chat_window_composing_cb (EmpathyChat *chat,
1442 gboolean is_composing,
1443 EmpathyChatWindow *window)
1445 chat_window_update_chat_tab (chat);
1449 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1452 EmpathyChatWindowPriv *priv;
1454 priv = GET_PRIV (window);
1456 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1460 chat_window_notification_closed_cb (NotifyNotification *notify,
1461 EmpathyChatWindow *self)
1463 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1465 g_object_unref (notify);
1466 if (priv->notification == notify) {
1467 priv->notification = NULL;
1472 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1473 EmpathyMessage *message,
1476 EmpathyContact *sender;
1477 const gchar *header;
1481 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1482 gboolean res, has_x_canonical_append;
1483 NotifyNotification *notification = priv->notification;
1485 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1488 res = g_settings_get_boolean (priv->gsettings_notif,
1489 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1496 sender = empathy_message_get_sender (message);
1497 header = empathy_contact_get_alias (sender);
1498 body = empathy_message_get_body (message);
1499 escaped = g_markup_escape_text (body, -1);
1500 has_x_canonical_append = empathy_notify_manager_has_capability (
1501 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1503 if (notification != NULL && !has_x_canonical_append) {
1504 /* if the notification server supports x-canonical-append, it is
1505 better to not use notify_notification_update to avoid
1506 overwriting the current notification message */
1507 notify_notification_update (notification,
1508 header, escaped, NULL);
1510 /* if the notification server supports x-canonical-append,
1511 the hint will be added, so that the message from the
1512 just created notification will be automatically appended
1513 to an existing notification with the same title.
1514 In this way the previous message will not be lost: the new
1515 message will appear below it, in the same notification */
1516 notification = notify_notification_new (header, escaped, NULL);
1518 if (priv->notification == NULL) {
1519 priv->notification = notification;
1522 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1524 tp_g_signal_connect_object (notification, "closed",
1525 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1527 if (has_x_canonical_append) {
1528 /* We have to set a not empty string to keep libnotify happy */
1529 notify_notification_set_hint_string (notification,
1530 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1533 notify_notification_set_hint (notification,
1534 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1535 g_variant_new_string ("im.received"));
1538 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1539 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1541 if (pixbuf != NULL) {
1542 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1543 g_object_unref (pixbuf);
1546 notify_notification_show (notification, NULL);
1552 chat_window_set_highlight_room_labels (EmpathyChat *chat)
1554 gchar *markup, *name;
1557 if (!empathy_chat_is_room (chat))
1560 name = empathy_chat_dup_name (chat);
1561 markup = g_markup_printf_escaped (
1562 "<span color=\"red\" weight=\"bold\">%s</span>",
1565 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1566 gtk_label_set_markup (GTK_LABEL (widget), markup);
1568 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1569 gtk_label_set_markup (GTK_LABEL (widget), markup);
1576 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1578 EmpathyChatWindowPriv *priv;
1581 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1583 priv = GET_PRIV (window);
1585 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1591 chat_window_new_message_cb (EmpathyChat *chat,
1592 EmpathyMessage *message,
1594 gboolean should_highlight,
1595 EmpathyChatWindow *window)
1597 EmpathyChatWindowPriv *priv;
1599 gboolean needs_urgency;
1600 EmpathyContact *sender;
1602 priv = GET_PRIV (window);
1604 has_focus = empathy_chat_window_has_focus (window);
1606 /* - if we're the sender, we play the sound if it's specified in the
1607 * preferences and we're not away.
1608 * - if we receive a message, we play the sound if it's specified in the
1609 * preferences and the window does not have focus on the chat receiving
1613 sender = empathy_message_get_sender (message);
1615 if (empathy_contact_is_user (sender)) {
1616 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1617 EMPATHY_SOUND_MESSAGE_OUTGOING);
1620 if (has_focus && priv->current_chat == chat) {
1621 /* window and tab are focused so consider the message to be read */
1623 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1624 empathy_chat_messages_read (chat);
1628 /* Update the chat tab if this is the first unread message */
1629 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1630 chat_window_update_chat_tab (chat);
1633 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1634 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1635 * an unamed MUC (msn-like).
1636 * In case of a MUC, we set urgency if either:
1637 * a) the chatroom's always_urgent property is TRUE
1638 * b) the message contains our alias
1640 if (empathy_chat_is_room (chat)) {
1643 EmpathyChatroom *chatroom;
1645 account = empathy_chat_get_account (chat);
1646 room = empathy_chat_get_id (chat);
1648 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1651 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1652 needs_urgency = TRUE;
1654 needs_urgency = should_highlight;
1657 needs_urgency = TRUE;
1660 if (needs_urgency) {
1661 chat_window_set_highlight_room_labels (chat);
1664 chat_window_set_urgency_hint (window, TRUE);
1667 /* Pending messages have already been displayed and notified in the
1668 * approver, so we don't display a notification and play a sound for those */
1670 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1671 EMPATHY_SOUND_MESSAGE_INCOMING);
1673 chat_window_show_or_update_notification (window, message, chat);
1677 /* update the number of unread messages and the window icon */
1678 chat_window_title_update (priv);
1679 chat_window_icon_update (priv, TRUE);
1683 chat_window_command_part (EmpathyChat *chat,
1686 EmpathyChat *chat_to_be_parted;
1687 EmpathyTpChat *tp_chat = NULL;
1689 if (strv[1] == NULL) {
1690 /* No chatroom ID specified */
1691 tp_chat = empathy_chat_get_tp_chat (chat);
1693 empathy_tp_chat_leave (tp_chat, "");
1696 chat_to_be_parted = empathy_chat_window_find_chat (
1697 empathy_chat_get_account (chat), strv[1], FALSE);
1699 if (chat_to_be_parted != NULL) {
1700 /* Found a chatroom matching the specified ID */
1701 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1703 empathy_tp_chat_leave (tp_chat, strv[2]);
1707 /* Going by the syntax of PART command:
1709 * /PART [<chatroom-ID>] [<reason>]
1711 * Chatroom-ID is not a must to specify a reason.
1712 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1713 * MUC then the current chatroom should be parted and srtv[1] should
1714 * be treated as part of the optional part-message. */
1715 message = g_strconcat (strv[1], " ", strv[2], NULL);
1716 tp_chat = empathy_chat_get_tp_chat (chat);
1718 empathy_tp_chat_leave (tp_chat, message);
1724 static GtkNotebook *
1725 notebook_create_window_cb (GtkNotebook *source,
1731 EmpathyChatWindowPriv *priv;
1732 EmpathyChatWindow *window, *new_window;
1735 chat = EMPATHY_CHAT (page);
1736 window = chat_window_find_chat (chat);
1738 new_window = empathy_chat_window_new ();
1739 priv = GET_PRIV (new_window);
1741 DEBUG ("Detach hook called");
1743 empathy_chat_window_move_chat (window, new_window, chat);
1745 gtk_widget_show (priv->dialog);
1746 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1752 chat_window_page_switched_cb (GtkNotebook *notebook,
1755 EmpathyChatWindow *window)
1757 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1758 EmpathyChat *chat = EMPATHY_CHAT (child);
1760 DEBUG ("Page switched");
1762 if (priv->page_added) {
1763 priv->page_added = FALSE;
1764 empathy_chat_scroll_down (chat);
1766 else if (priv->current_chat == chat) {
1770 priv->current_chat = chat;
1771 empathy_chat_messages_read (chat);
1773 chat_window_update_chat_tab (chat);
1777 chat_window_page_added_cb (GtkNotebook *notebook,
1780 EmpathyChatWindow *window)
1782 EmpathyChatWindowPriv *priv;
1785 priv = GET_PRIV (window);
1787 /* If we just received DND to the same window, we don't want
1788 * to do anything here like removing the tab and then readding
1789 * it, so we return here and in "page-added".
1791 if (priv->dnd_same_window) {
1792 DEBUG ("Page added (back to the same window)");
1793 priv->dnd_same_window = FALSE;
1797 DEBUG ("Page added");
1799 /* Get chat object */
1800 chat = EMPATHY_CHAT (child);
1802 /* Connect chat signals for this window */
1803 g_signal_connect (chat, "composing",
1804 G_CALLBACK (chat_window_composing_cb),
1806 g_signal_connect (chat, "new-message",
1807 G_CALLBACK (chat_window_new_message_cb),
1809 g_signal_connect (chat, "part-command-entered",
1810 G_CALLBACK (chat_window_command_part),
1812 g_signal_connect (chat, "notify::tp-chat",
1813 G_CALLBACK (chat_window_update_chat_tab),
1816 /* Set flag so we know to perform some special operations on
1817 * switch page due to the new page being added.
1819 priv->page_added = TRUE;
1821 /* Get list of chats up to date */
1822 priv->chats = g_list_append (priv->chats, chat);
1824 chat_window_update_chat_tab (chat);
1828 chat_window_page_removed_cb (GtkNotebook *notebook,
1831 EmpathyChatWindow *window)
1833 EmpathyChatWindowPriv *priv;
1836 priv = GET_PRIV (window);
1838 /* If we just received DND to the same window, we don't want
1839 * to do anything here like removing the tab and then readding
1840 * it, so we return here and in "page-added".
1842 if (priv->dnd_same_window) {
1843 DEBUG ("Page removed (and will be readded to same window)");
1847 DEBUG ("Page removed");
1849 /* Get chat object */
1850 chat = EMPATHY_CHAT (child);
1852 /* Disconnect all signal handlers for this chat and this window */
1853 g_signal_handlers_disconnect_by_func (chat,
1854 G_CALLBACK (chat_window_composing_cb),
1856 g_signal_handlers_disconnect_by_func (chat,
1857 G_CALLBACK (chat_window_new_message_cb),
1859 g_signal_handlers_disconnect_by_func (chat,
1860 G_CALLBACK (chat_window_update_chat_tab),
1863 /* Keep list of chats up to date */
1864 priv->chats = g_list_remove (priv->chats, chat);
1865 empathy_chat_messages_read (chat);
1867 if (priv->chats == NULL) {
1868 g_object_unref (window);
1870 chat_window_update (window, TRUE);
1875 chat_window_focus_in_event_cb (GtkWidget *widget,
1877 EmpathyChatWindow *window)
1879 EmpathyChatWindowPriv *priv;
1881 priv = GET_PRIV (window);
1883 empathy_chat_messages_read (priv->current_chat);
1885 chat_window_set_urgency_hint (window, FALSE);
1887 /* Update the title, since we now mark all unread messages as read. */
1888 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1894 chat_window_drag_drop (GtkWidget *widget,
1895 GdkDragContext *context,
1899 EmpathyChatWindow *window)
1902 EmpathyChatWindowPriv *priv;
1904 priv = GET_PRIV (window);
1906 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1907 if (target == GDK_NONE)
1908 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1910 if (target != GDK_NONE) {
1911 gtk_drag_get_data (widget, context, target, time_);
1919 chat_window_drag_motion (GtkWidget *widget,
1920 GdkDragContext *context,
1924 EmpathyChatWindow *window)
1927 EmpathyChatWindowPriv *priv;
1929 priv = GET_PRIV (window);
1931 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1932 if (target != GDK_NONE) {
1933 /* This is a file drag. Ensure the contact is online and set the
1934 drag type to COPY. Note that it's possible that the tab will
1935 be switched by GTK+ after a timeout from drag_motion without
1936 getting another drag_motion to disable the drop. You have
1937 to hold your mouse really still.
1939 EmpathyContact *contact;
1941 priv = GET_PRIV (window);
1942 contact = empathy_chat_get_remote_contact (priv->current_chat);
1943 /* contact is NULL for multi-user chats. We don't do
1944 * file transfers to MUCs. We also don't send files
1945 * to offline contacts or contacts that don't support
1948 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1949 gdk_drag_status (context, 0, time_);
1952 if (!(empathy_contact_get_capabilities (contact)
1953 & EMPATHY_CAPABILITIES_FT)) {
1954 gdk_drag_status (context, 0, time_);
1957 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1961 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1962 if (target != GDK_NONE) {
1963 /* This is a drag of a contact from a contact list. Set to COPY.
1964 FIXME: If this drag is to a MUC window, it invites the user.
1965 Otherwise, it opens a chat. Should we use a different drag
1966 type for invites? Should we allow ASK?
1968 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1976 chat_window_drag_data_received (GtkWidget *widget,
1977 GdkDragContext *context,
1980 GtkSelectionData *selection,
1983 EmpathyChatWindow *window)
1985 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1986 EmpathyChat *chat = NULL;
1987 EmpathyChatWindow *old_window;
1988 TpAccount *account = NULL;
1989 EmpathyClientFactory *factory;
1992 const gchar *account_id;
1993 const gchar *contact_id;
1995 id = (const gchar*) gtk_selection_data_get_data (selection);
1997 factory = empathy_client_factory_dup ();
1999 DEBUG ("DND contact from roster with id:'%s'", id);
2001 strv = g_strsplit (id, ":", 2);
2002 if (g_strv_length (strv) == 2) {
2003 account_id = strv[0];
2004 contact_id = strv[1];
2006 tp_simple_client_factory_ensure_account (
2007 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2010 g_object_unref (factory);
2011 if (account != NULL)
2012 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2015 if (account == NULL) {
2017 gtk_drag_finish (context, FALSE, FALSE, time_);
2022 empathy_chat_with_contact_id (
2023 account, contact_id,
2024 empathy_get_current_action_time (),
2032 old_window = chat_window_find_chat (chat);
2034 if (old_window == window) {
2035 gtk_drag_finish (context, TRUE, FALSE, time_);
2039 empathy_chat_window_move_chat (old_window, window, chat);
2041 empathy_chat_window_add_chat (window, chat);
2044 /* Added to take care of any outstanding chat events */
2045 empathy_chat_window_present_chat (chat,
2046 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2048 /* We should return TRUE to remove the data when doing
2049 * GDK_ACTION_MOVE, but we don't here otherwise it has
2050 * weird consequences, and we handle that internally
2051 * anyway with add_chat () and remove_chat ().
2053 gtk_drag_finish (context, TRUE, FALSE, time_);
2055 else if (info == DND_DRAG_TYPE_URI_LIST) {
2056 EmpathyChatWindowPriv *priv;
2057 EmpathyContact *contact;
2060 priv = GET_PRIV (window);
2061 contact = empathy_chat_get_remote_contact (priv->current_chat);
2063 /* contact is NULL when current_chat is a multi-user chat.
2064 * We don't do file transfers to MUCs, so just cancel the drag.
2066 if (contact == NULL) {
2067 gtk_drag_finish (context, TRUE, FALSE, time_);
2071 data = (const gchar *) gtk_selection_data_get_data (selection);
2072 empathy_send_file_from_uri_list (contact, data);
2074 gtk_drag_finish (context, TRUE, FALSE, time_);
2076 else if (info == DND_DRAG_TYPE_TAB) {
2078 EmpathyChatWindow *old_window = NULL;
2082 chat = (void *) gtk_selection_data_get_data (selection);
2083 old_window = chat_window_find_chat (*chat);
2086 EmpathyChatWindowPriv *priv;
2088 priv = GET_PRIV (window);
2089 priv->dnd_same_window = (old_window == window);
2090 DEBUG ("DND tab (within same window: %s)",
2091 priv->dnd_same_window ? "Yes" : "No");
2094 DEBUG ("DND from unknown source");
2095 gtk_drag_finish (context, FALSE, FALSE, time_);
2100 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2101 guint num_chats_in_manager,
2102 EmpathyChatWindow *window)
2104 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2106 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2107 num_chats_in_manager > 0);
2111 chat_window_finalize (GObject *object)
2113 EmpathyChatWindow *window;
2114 EmpathyChatWindowPriv *priv;
2116 window = EMPATHY_CHAT_WINDOW (object);
2117 priv = GET_PRIV (window);
2119 DEBUG ("Finalized: %p", object);
2121 g_object_unref (priv->ui_manager);
2122 g_object_unref (priv->chatroom_manager);
2123 g_object_unref (priv->notify_mgr);
2124 g_object_unref (priv->gsettings_chat);
2125 g_object_unref (priv->gsettings_notif);
2126 g_object_unref (priv->gsettings_ui);
2127 g_object_unref (priv->sound_mgr);
2129 if (priv->notification != NULL) {
2130 notify_notification_close (priv->notification, NULL);
2131 priv->notification = NULL;
2134 if (priv->contact_targets) {
2135 gtk_target_list_unref (priv->contact_targets);
2137 if (priv->file_targets) {
2138 gtk_target_list_unref (priv->file_targets);
2141 if (priv->chat_manager) {
2142 g_signal_handler_disconnect (priv->chat_manager,
2143 priv->chat_manager_chats_changed_id);
2144 g_object_unref (priv->chat_manager);
2145 priv->chat_manager = NULL;
2148 chat_windows = g_list_remove (chat_windows, window);
2149 gtk_widget_destroy (priv->dialog);
2151 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2155 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2157 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2159 object_class->finalize = chat_window_finalize;
2161 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2165 empathy_chat_window_init (EmpathyChatWindow *window)
2168 GtkAccelGroup *accel_group;
2173 GtkWidget *chat_vbox;
2175 EmpathySmileyManager *smiley_manager;
2176 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2177 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2179 window->priv = priv;
2180 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2181 gui = empathy_builder_get_file (filename,
2182 "chat_window", &priv->dialog,
2183 "chat_vbox", &chat_vbox,
2184 "ui_manager", &priv->ui_manager,
2185 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2186 "menu_conv_favorite", &priv->menu_conv_favorite,
2187 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2188 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2189 "menu_edit_cut", &priv->menu_edit_cut,
2190 "menu_edit_copy", &priv->menu_edit_copy,
2191 "menu_edit_paste", &priv->menu_edit_paste,
2192 "menu_edit_find", &priv->menu_edit_find,
2193 "menu_tabs_next", &priv->menu_tabs_next,
2194 "menu_tabs_prev", &priv->menu_tabs_prev,
2195 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2196 "menu_tabs_left", &priv->menu_tabs_left,
2197 "menu_tabs_right", &priv->menu_tabs_right,
2198 "menu_tabs_detach", &priv->menu_tabs_detach,
2202 empathy_builder_connect (gui, window,
2203 "menu_conv", "activate", chat_window_conv_activate_cb,
2204 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2205 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2206 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2207 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2208 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2209 "menu_conv_close", "activate", chat_window_close_activate_cb,
2210 "menu_edit", "activate", chat_window_edit_activate_cb,
2211 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2212 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2213 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2214 "menu_edit_find", "activate", chat_window_find_activate_cb,
2215 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2216 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2217 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2218 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2219 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2220 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2221 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2222 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2225 g_object_ref (priv->ui_manager);
2226 g_object_unref (gui);
2228 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2229 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2230 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2231 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2233 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2235 priv->notebook = gtk_notebook_new ();
2237 g_signal_connect (priv->notebook, "create-window",
2238 G_CALLBACK (notebook_create_window_cb), window);
2240 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2241 "EmpathyChatWindow");
2242 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2243 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2244 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2245 gtk_widget_show (priv->notebook);
2248 accel_group = gtk_accel_group_new ();
2249 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2251 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2252 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2255 gtk_accel_group_connect (accel_group,
2262 g_object_unref (accel_group);
2264 /* Set up drag target lists */
2265 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2266 G_N_ELEMENTS (drag_types_dest_contact));
2267 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2268 G_N_ELEMENTS (drag_types_dest_file));
2270 /* Set up smiley menu */
2271 smiley_manager = empathy_smiley_manager_dup_singleton ();
2272 submenu = empathy_smiley_menu_new (smiley_manager,
2273 chat_window_insert_smiley_activate_cb,
2275 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2276 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2277 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2278 g_object_unref (smiley_manager);
2280 /* Set up signals we can't do with ui file since we may need to
2281 * block/unblock them at some later stage.
2284 g_signal_connect (priv->dialog,
2286 G_CALLBACK (chat_window_delete_event_cb),
2288 g_signal_connect (priv->dialog,
2290 G_CALLBACK (chat_window_focus_in_event_cb),
2292 g_signal_connect_after (priv->notebook,
2294 G_CALLBACK (chat_window_page_switched_cb),
2296 g_signal_connect (priv->notebook,
2298 G_CALLBACK (chat_window_page_added_cb),
2300 g_signal_connect (priv->notebook,
2302 G_CALLBACK (chat_window_page_removed_cb),
2305 /* Set up drag and drop */
2306 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2307 GTK_DEST_DEFAULT_HIGHLIGHT,
2309 G_N_ELEMENTS (drag_types_dest),
2310 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2312 /* connect_after to allow GtkNotebook's built-in tab switching */
2313 g_signal_connect_after (priv->notebook,
2315 G_CALLBACK (chat_window_drag_motion),
2317 g_signal_connect (priv->notebook,
2318 "drag-data-received",
2319 G_CALLBACK (chat_window_drag_data_received),
2321 g_signal_connect (priv->notebook,
2323 G_CALLBACK (chat_window_drag_drop),
2326 chat_windows = g_list_prepend (chat_windows, window);
2328 /* Set up private details */
2330 priv->current_chat = NULL;
2331 priv->notification = NULL;
2333 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2335 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2336 priv->chat_manager_chats_changed_id =
2337 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2338 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2341 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2342 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2346 /* Returns the window to open a new tab in if there is a suitable window,
2347 * otherwise, returns NULL indicating that a new window should be added.
2349 static EmpathyChatWindow *
2350 empathy_chat_window_get_default (gboolean room)
2352 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2354 gboolean separate_windows = TRUE;
2356 separate_windows = g_settings_get_boolean (gsettings,
2357 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2359 g_object_unref (gsettings);
2361 if (separate_windows) {
2362 /* Always create a new window */
2366 for (l = chat_windows; l; l = l->next) {
2367 EmpathyChatWindow *chat_window;
2368 guint nb_rooms, nb_private;
2370 chat_window = l->data;
2372 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2374 /* Skip the window if there aren't any rooms in it */
2375 if (room && nb_rooms == 0)
2378 /* Skip the window if there aren't any 1-1 chats in it */
2379 if (!room && nb_private == 0)
2389 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2392 EmpathyChatWindowPriv *priv;
2394 GtkWidget *popup_label;
2396 GValue value = { 0, };
2398 g_return_if_fail (window != NULL);
2399 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2401 priv = GET_PRIV (window);
2403 /* Reference the chat object */
2404 g_object_ref (chat);
2406 /* If this window has just been created, position it */
2407 if (priv->chats == NULL) {
2408 const gchar *name = "chat-window";
2409 gboolean separate_windows;
2411 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2412 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2414 if (empathy_chat_is_room (chat))
2415 name = "room-window";
2417 if (separate_windows) {
2420 /* Save current position of the window */
2421 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2423 /* First bind to the 'generic' name. So new window for which we didn't
2424 * save a geometry yet will have the geometry of the last saved
2425 * window (bgo #601191). */
2426 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2428 /* Restore previous position of the window so the newly created window
2429 * won't be in the same position as the latest saved window and so
2430 * completely hide it. */
2431 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2433 /* Then bind it to the name of the contact/room so we'll save the
2434 * geometry specific to this window */
2435 name = empathy_chat_get_id (chat);
2438 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2441 child = GTK_WIDGET (chat);
2442 label = chat_window_create_label (window, chat, TRUE);
2443 popup_label = chat_window_create_label (window, chat, FALSE);
2444 gtk_widget_show (child);
2446 g_signal_connect (chat, "notify::name",
2447 G_CALLBACK (chat_window_chat_notify_cb),
2449 g_signal_connect (chat, "notify::subject",
2450 G_CALLBACK (chat_window_chat_notify_cb),
2452 g_signal_connect (chat, "notify::remote-contact",
2453 G_CALLBACK (chat_window_chat_notify_cb),
2455 g_signal_connect (chat, "notify::sms-channel",
2456 G_CALLBACK (chat_window_chat_notify_cb),
2458 g_signal_connect (chat, "notify::n-messages-sending",
2459 G_CALLBACK (chat_window_chat_notify_cb),
2461 g_signal_connect (chat, "notify::nb-unread-messages",
2462 G_CALLBACK (chat_window_chat_notify_cb),
2464 chat_window_chat_notify_cb (chat);
2466 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2467 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2468 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2469 g_value_init (&value, G_TYPE_BOOLEAN);
2470 g_value_set_boolean (&value, TRUE);
2471 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2472 child, "tab-expand" , &value);
2473 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2474 child, "tab-fill" , &value);
2475 g_value_unset (&value);
2477 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2481 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2484 EmpathyChatWindowPriv *priv;
2486 EmpathyContact *remote_contact;
2487 EmpathyChatManager *chat_manager;
2489 g_return_if_fail (window != NULL);
2490 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2492 priv = GET_PRIV (window);
2494 g_signal_handlers_disconnect_by_func (chat,
2495 chat_window_chat_notify_cb,
2497 remote_contact = g_object_get_data (G_OBJECT (chat),
2498 "chat-window-remote-contact");
2499 if (remote_contact) {
2500 g_signal_handlers_disconnect_by_func (remote_contact,
2501 chat_window_update_chat_tab,
2505 chat_manager = empathy_chat_manager_dup_singleton ();
2506 empathy_chat_manager_closed_chat (chat_manager, chat);
2507 g_object_unref (chat_manager);
2509 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2511 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2513 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2515 g_object_unref (chat);
2519 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2520 EmpathyChatWindow *new_window,
2525 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2526 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2527 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2529 widget = GTK_WIDGET (chat);
2531 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2532 G_OBJECT (widget)->ref_count);
2534 /* We reference here to make sure we don't loose the widget
2535 * and the EmpathyChat object during the move.
2537 g_object_ref (chat);
2538 g_object_ref (widget);
2540 empathy_chat_window_remove_chat (old_window, chat);
2541 empathy_chat_window_add_chat (new_window, chat);
2543 g_object_unref (widget);
2544 g_object_unref (chat);
2548 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2551 EmpathyChatWindowPriv *priv;
2554 g_return_if_fail (window != NULL);
2555 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2557 priv = GET_PRIV (window);
2559 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2561 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2566 empathy_chat_window_find_chat (TpAccount *account,
2568 gboolean sms_channel)
2572 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2574 for (l = chat_windows; l; l = l->next) {
2575 EmpathyChatWindowPriv *priv;
2576 EmpathyChatWindow *window;
2580 priv = GET_PRIV (window);
2582 for (ll = priv->chats; ll; ll = ll->next) {
2587 if (account == empathy_chat_get_account (chat) &&
2588 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2589 sms_channel == empathy_chat_is_sms_channel (chat)) {
2599 empathy_chat_window_present_chat (EmpathyChat *chat,
2602 EmpathyChatWindow *window;
2603 EmpathyChatWindowPriv *priv;
2604 guint32 x_timestamp;
2606 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2608 window = chat_window_find_chat (chat);
2610 /* If the chat has no window, create one */
2611 if (window == NULL) {
2612 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2614 window = empathy_chat_window_new ();
2616 /* we want to display the newly created window even if we don't present
2618 priv = GET_PRIV (window);
2619 gtk_widget_show (priv->dialog);
2622 empathy_chat_window_add_chat (window, chat);
2625 /* Don't force the window to show itself when it wasn't
2626 * an action by the user
2628 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2631 priv = GET_PRIV (window);
2633 if (x_timestamp != GDK_CURRENT_TIME) {
2634 /* Don't present or switch tab if the action was earlier than the
2635 * last actions X time, accounting for overflow and the first ever
2638 if (priv->x_user_action_time != 0
2639 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2642 priv->x_user_action_time = x_timestamp;
2645 empathy_chat_window_switch_to_chat (window, chat);
2647 /* Don't use empathy_window_present_with_time () which would move the window
2648 * to our current desktop but move to the window's desktop instead. This is
2649 * more coherent with Shell's 'app is ready' notication which moves the view
2650 * to the app desktop rather than moving the app itself. */
2651 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2653 gtk_widget_grab_focus (chat->input_text_view);
2657 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2661 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2663 guint _nb_rooms = 0, _nb_private = 0;
2665 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2666 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2672 if (nb_rooms != NULL)
2673 *nb_rooms = _nb_rooms;
2674 if (nb_private != NULL)
2675 *nb_private = _nb_private;