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>
29 #include "empathy-chat-window.h"
32 #include <glib/gi18n.h>
34 #include "empathy-about-dialog.h"
35 #include "empathy-chat-manager.h"
36 #include "empathy-chatroom-manager.h"
37 #include "empathy-client-factory.h"
38 #include "empathy-geometry.h"
39 #include "empathy-gsettings.h"
40 #include "empathy-images.h"
41 #include "empathy-individual-manager.h"
42 #include "empathy-invite-participant-dialog.h"
43 #include "empathy-notify-manager.h"
44 #include "empathy-request-util.h"
45 #include "empathy-smiley-manager.h"
46 #include "empathy-sound-manager.h"
47 #include "empathy-ui-utils.h"
48 #include "empathy-utils.h"
50 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
51 #include "empathy-debug.h"
53 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
55 #define X_EARLIER_OR_EQL(t1, t2) \
56 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
57 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
62 PROP_INDIVIDUAL_MGR = 1
65 struct _EmpathyChatWindowPriv
67 EmpathyChat *current_chat;
70 gboolean dnd_same_window;
71 EmpathyChatroomManager *chatroom_manager;
72 EmpathyNotifyManager *notify_mgr;
73 EmpathyIndividualManager *individual_mgr;
75 NotifyNotification *notification;
77 GtkTargetList *contact_targets;
78 GtkTargetList *file_targets;
80 EmpathyChatManager *chat_manager;
81 gulong chat_manager_chats_changed_id;
84 GtkUIManager *ui_manager;
85 GtkAction *menu_conv_insert_smiley;
86 GtkAction *menu_conv_favorite;
87 GtkAction *menu_conv_always_urgent;
88 GtkAction *menu_conv_toggle_contacts;
90 GtkAction *menu_edit_cut;
91 GtkAction *menu_edit_copy;
92 GtkAction *menu_edit_paste;
93 GtkAction *menu_edit_find;
95 GtkAction *menu_tabs_next;
96 GtkAction *menu_tabs_prev;
97 GtkAction *menu_tabs_undo_close_tab;
98 GtkAction *menu_tabs_left;
99 GtkAction *menu_tabs_right;
100 GtkAction *menu_tabs_detach;
102 /* Last user action time we acted upon to show a tab */
103 guint32 x_user_action_time;
105 GSettings *gsettings_chat;
106 GSettings *gsettings_notif;
107 GSettings *gsettings_ui;
109 EmpathySoundManager *sound_mgr;
111 gboolean updating_menu;
114 static GList *chat_windows = NULL;
116 static const guint tab_accel_keys[] =
118 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
119 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
124 DND_DRAG_TYPE_CONTACT_ID,
125 DND_DRAG_TYPE_INDIVIDUAL_ID,
126 DND_DRAG_TYPE_URI_LIST,
130 static const GtkTargetEntry drag_types_dest[] =
132 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
133 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
134 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
135 /* FIXME: disabled because of bug #640513
136 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
137 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
141 static const GtkTargetEntry drag_types_dest_contact[] =
143 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
144 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
147 static const GtkTargetEntry drag_types_dest_file[] =
149 /* must be first to be prioritized, in order to receive the
150 * note's file path from Tomboy instead of an URI */
151 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
152 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
155 static void chat_window_update (EmpathyChatWindow *window,
156 gboolean update_contact_menu);
158 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
161 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
164 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
165 EmpathyChatWindow *new_window,
168 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
172 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_WINDOW)
175 chat_window_accel_cb (GtkAccelGroup *accelgroup,
179 EmpathyChatWindow *self)
184 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
186 if (tab_accel_keys[i] == key)
194 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
197 static EmpathyChatWindow *
198 chat_window_find_chat (EmpathyChat *chat)
202 for (l = chat_windows; l; l = l->next)
204 EmpathyChatWindow *window = l->data;
206 ll = g_list_find (window->priv->chats, chat);
215 remove_all_chats (EmpathyChatWindow *self)
219 while (self->priv->chats)
220 empathy_chat_window_remove_chat (self, self->priv->chats->data);
222 g_object_unref (self);
226 confirm_close_response_cb (GtkWidget *dialog,
228 EmpathyChatWindow *window)
232 chat = g_object_get_data (G_OBJECT (dialog), "chat");
234 gtk_widget_destroy (dialog);
236 if (response != GTK_RESPONSE_ACCEPT)
240 empathy_chat_window_remove_chat (window, chat);
242 remove_all_chats (window);
246 confirm_close (EmpathyChatWindow *self,
247 gboolean close_window,
252 gchar *primary, *secondary;
254 g_return_if_fail (n_rooms > 0);
257 g_return_if_fail (chat == NULL);
259 g_return_if_fail (chat != NULL);
261 /* If there are no chats in this window, how could we possibly have got
264 g_return_if_fail (self->priv->chats != NULL);
266 /* Treat closing a window which only has one tab exactly like closing
269 if (close_window && self->priv->chats->next == NULL)
271 close_window = FALSE;
272 chat = self->priv->chats->data;
277 primary = g_strdup (_("Close this window?"));
281 gchar *chat_name = empathy_chat_dup_name (chat);
282 secondary = g_strdup_printf (
283 _("Closing this window will leave %s. You will "
284 "not receive any further messages until you "
291 secondary = g_strdup_printf (
292 /* Note to translators: the number of chats will
293 * always be at least 2.
296 "Closing this window will leave a chat room. You will "
297 "not receive any further messages until you rejoin it.",
298 "Closing this window will leave %u chat rooms. You will "
299 "not receive any further messages until you rejoin them.",
306 gchar *chat_name = empathy_chat_dup_name (chat);
307 primary = g_strdup_printf (_("Leave %s?"), chat_name);
308 secondary = g_strdup (
309 _("You will not receive any further messages from this chat "
310 "room until you rejoin it."));
314 dialog = gtk_message_dialog_new (
316 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
321 gtk_window_set_title (GTK_WINDOW (dialog), "");
322 g_object_set (dialog, "secondary-text", secondary, NULL);
327 gtk_dialog_add_button (GTK_DIALOG (dialog),
328 close_window ? _("Close window") : _("Leave room"),
329 GTK_RESPONSE_ACCEPT);
330 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
331 GTK_RESPONSE_ACCEPT);
334 g_object_set_data (G_OBJECT (dialog), "chat", chat);
336 g_signal_connect (dialog, "response",
337 G_CALLBACK (confirm_close_response_cb), self);
339 gtk_window_present (GTK_WINDOW (dialog));
342 /* Returns TRUE if we should check if the user really wants to leave. If it's
343 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
344 * the user is actually in the room as opposed to having been kicked or gone
345 * offline or something), then we should check.
348 chat_needs_close_confirmation (EmpathyChat *chat)
350 return (empathy_chat_is_room (chat) &&
351 empathy_chat_get_tp_chat (chat) != NULL);
355 maybe_close_chat (EmpathyChatWindow *window,
358 g_return_if_fail (chat != NULL);
360 if (chat_needs_close_confirmation (chat))
361 confirm_close (window, FALSE, 1, chat);
363 empathy_chat_window_remove_chat (window, chat);
367 chat_window_close_clicked_cb (GtkAction *action,
370 EmpathyChatWindow *window;
372 window = chat_window_find_chat (chat);
373 maybe_close_chat (window, chat);
377 chat_tab_style_updated_cb (GtkWidget *hbox,
381 int char_width, h, w;
382 PangoContext *context;
383 const PangoFontDescription *font_desc;
384 PangoFontMetrics *metrics;
386 button = g_object_get_data (G_OBJECT (user_data),
387 "chat-window-tab-close-button");
388 context = gtk_widget_get_pango_context (hbox);
390 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
391 GTK_STATE_FLAG_NORMAL);
393 metrics = pango_context_get_metrics (context, font_desc,
394 pango_context_get_language (context));
395 char_width = pango_font_metrics_get_approximate_char_width (metrics);
396 pango_font_metrics_unref (metrics);
398 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
399 GTK_ICON_SIZE_MENU, &w, &h);
401 /* Request at least about 12 chars width plus at least space for the status
402 * image and the close button */
403 gtk_widget_set_size_request (hbox,
404 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
406 gtk_widget_set_size_request (button, w, h);
410 create_close_button (void)
412 GtkWidget *button, *image;
413 GtkStyleContext *context;
415 button = gtk_button_new ();
417 context = gtk_widget_get_style_context (button);
418 gtk_style_context_add_class (context, "empathy-tab-close-button");
420 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
421 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
423 /* We don't want focus/keynav for the button to avoid clutter, and
424 * Ctrl-W works anyway.
426 gtk_widget_set_can_focus (button, FALSE);
427 gtk_widget_set_can_default (button, FALSE);
429 image = gtk_image_new_from_icon_name ("window-close-symbolic",
431 gtk_widget_show (image);
433 gtk_container_add (GTK_CONTAINER (button), image);
439 chat_window_create_label (EmpathyChatWindow *window,
441 gboolean is_tab_label)
444 GtkWidget *name_label;
445 GtkWidget *status_image;
446 GtkWidget *event_box;
447 GtkWidget *event_box_hbox;
448 PangoAttrList *attr_list;
449 PangoAttribute *attr;
451 /* The spacing between the button and the label. */
452 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
454 event_box = gtk_event_box_new ();
455 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
457 name_label = gtk_label_new (NULL);
459 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
461 attr_list = pango_attr_list_new ();
462 attr = pango_attr_scale_new (1/1.2);
463 attr->start_index = 0;
464 attr->end_index = -1;
465 pango_attr_list_insert (attr_list, attr);
466 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
467 pango_attr_list_unref (attr_list);
469 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
470 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
471 g_object_set_data (G_OBJECT (chat),
472 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
475 status_image = gtk_image_new ();
477 /* Spacing between the icon and label. */
478 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
480 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
481 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
483 g_object_set_data (G_OBJECT (chat),
484 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
486 g_object_set_data (G_OBJECT (chat),
487 is_tab_label ? "chat-window-tab-tooltip-widget" :
488 "chat-window-menu-tooltip-widget",
491 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
492 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
496 GtkWidget *close_button;
497 GtkWidget *sending_spinner;
499 sending_spinner = gtk_spinner_new ();
501 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
503 g_object_set_data (G_OBJECT (chat),
504 "chat-window-tab-sending-spinner",
507 close_button = create_close_button ();
508 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
511 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
513 g_signal_connect (close_button,
515 G_CALLBACK (chat_window_close_clicked_cb), chat);
517 /* React to theme changes and also setup the size correctly. */
518 g_signal_connect (hbox, "style-updated",
519 G_CALLBACK (chat_tab_style_updated_cb), chat);
522 gtk_widget_show_all (hbox);
528 _submenu_notify_visible_changed_cb (GObject *object,
532 g_signal_handlers_disconnect_by_func (object,
533 _submenu_notify_visible_changed_cb, userdata);
535 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
539 chat_window_menu_context_update (EmpathyChatWindow *self,
544 gboolean wrap_around;
545 gboolean is_connected;
548 page_num = gtk_notebook_get_current_page (
549 GTK_NOTEBOOK (self->priv->notebook));
550 first_page = (page_num == 0);
551 last_page = (page_num == (num_pages - 1));
552 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
554 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
556 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
558 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
560 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
561 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
562 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
563 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
567 chat_window_conversation_menu_update (EmpathyChatWindow *self)
569 EmpathyTpChat *tp_chat;
570 TpConnection *connection;
572 gboolean sensitive = FALSE;
574 g_return_if_fail (self->priv->current_chat != NULL);
576 action = gtk_ui_manager_get_action (self->priv->ui_manager,
577 "/chats_menubar/menu_conv/menu_conv_invite_participant");
578 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
582 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
584 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
585 (tp_connection_get_status (connection, NULL) ==
586 TP_CONNECTION_STATUS_CONNECTED);
589 gtk_action_set_sensitive (action, sensitive);
593 chat_window_contact_menu_update (EmpathyChatWindow *self)
595 GtkWidget *menu, *submenu, *orig_submenu;
597 if (self->priv->updating_menu)
599 self->priv->updating_menu = TRUE;
601 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
602 "/chats_menubar/menu_contact");
603 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
605 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
607 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
611 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
612 g_object_set_data (G_OBJECT (submenu), "window", self);
614 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
615 gtk_widget_show (menu);
616 gtk_widget_set_sensitive (menu, TRUE);
620 gtk_widget_set_sensitive (menu, FALSE);
625 tp_g_signal_connect_object (orig_submenu,
627 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
630 self->priv->updating_menu = FALSE;
634 get_all_unread_messages (EmpathyChatWindow *self)
639 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
640 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
646 get_window_title_name (EmpathyChatWindow *self)
648 gchar *active_name, *ret;
650 guint current_unread_msgs;
652 nb_chats = g_list_length (self->priv->chats);
653 g_assert (nb_chats > 0);
655 active_name = empathy_chat_dup_name (self->priv->current_chat);
657 current_unread_msgs = empathy_chat_get_nb_unread_messages (
658 self->priv->current_chat);
663 if (current_unread_msgs == 0)
664 ret = g_strdup (active_name);
666 ret = g_strdup_printf (ngettext (
668 "%s (%d unread)", current_unread_msgs),
669 active_name, current_unread_msgs);
673 guint nb_others = nb_chats - 1;
674 guint all_unread_msgs;
676 all_unread_msgs = get_all_unread_messages (self);
678 if (all_unread_msgs == 0)
680 /* no unread message */
681 ret = g_strdup_printf (ngettext (
683 "%s (and %u others)", nb_others),
684 active_name, nb_others);
686 else if (all_unread_msgs == current_unread_msgs)
688 /* unread messages are in the current tab */
689 ret = g_strdup_printf (ngettext (
691 "%s (%d unread)", current_unread_msgs),
692 active_name, current_unread_msgs);
694 else if (current_unread_msgs == 0)
696 /* unread messages are in other tabs */
697 ret = g_strdup_printf (ngettext (
698 "%s (%d unread from others)",
699 "%s (%d unread from others)",
701 active_name, all_unread_msgs);
705 /* unread messages are in all the tabs */
706 ret = g_strdup_printf (ngettext (
707 "%s (%d unread from all)",
708 "%s (%d unread from all)",
710 active_name, all_unread_msgs);
714 g_free (active_name);
720 chat_window_title_update (EmpathyChatWindow *self)
724 name = get_window_title_name (self);
725 gtk_window_set_title (GTK_WINDOW (self), name);
730 chat_window_icon_update (EmpathyChatWindow *self,
731 gboolean new_messages)
734 EmpathyContact *remote_contact;
735 gboolean avatar_in_icon;
738 n_chats = g_list_length (self->priv->chats);
740 /* Update window icon */
743 gtk_window_set_icon_name (GTK_WINDOW (self),
744 EMPATHY_IMAGE_MESSAGE);
748 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
749 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
751 if (n_chats == 1 && avatar_in_icon)
753 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
754 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
756 gtk_window_set_icon (GTK_WINDOW (self), icon);
759 g_object_unref (icon);
763 gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
769 chat_window_close_button_update (EmpathyChatWindow *self,
773 GtkWidget *chat_close_button;
778 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
779 chat_close_button = g_object_get_data (G_OBJECT (chat),
780 "chat-window-tab-close-button");
781 gtk_widget_hide (chat_close_button);
785 for (i=0; i<num_pages; i++)
787 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
788 chat_close_button = g_object_get_data (G_OBJECT (chat),
789 "chat-window-tab-close-button");
790 gtk_widget_show (chat_close_button);
796 chat_window_update (EmpathyChatWindow *self,
797 gboolean update_contact_menu)
801 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
803 /* Update Tab menu */
804 chat_window_menu_context_update (self, num_pages);
806 chat_window_conversation_menu_update (self);
808 /* If this update is due to a focus-in event, we know the menu will be
809 the same as when we last left it, so no work to do. Besides, if we
810 swap out the menu on a focus-in, we may confuse any external global
812 if (update_contact_menu)
814 chat_window_contact_menu_update (self);
817 chat_window_title_update (self);
819 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
821 chat_window_close_button_update (self, num_pages);
825 append_markup_printf (GString *string,
832 va_start (args, format);
834 tmp = g_markup_vprintf_escaped (format, args);
835 g_string_append (string, tmp);
842 chat_window_update_chat_tab_full (EmpathyChat *chat,
843 gboolean update_contact_menu)
845 EmpathyChatWindow *self;
846 EmpathyContact *remote_contact;
850 const gchar *subject;
851 const gchar *status = NULL;
855 const gchar *icon_name;
856 GtkWidget *tab_image;
857 GtkWidget *menu_image;
858 GtkWidget *sending_spinner;
861 self = chat_window_find_chat (chat);
865 /* Get information */
866 name = empathy_chat_dup_name (chat);
867 account = empathy_chat_get_account (chat);
868 subject = empathy_chat_get_subject (chat);
869 remote_contact = empathy_chat_get_remote_contact (chat);
871 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
873 name, tp_proxy_get_object_path (account), subject, remote_contact);
875 /* Update tab image */
876 if (empathy_chat_get_tp_chat (chat) == NULL)
878 /* No TpChat, we are disconnected */
881 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
883 icon_name = EMPATHY_IMAGE_MESSAGE;
885 else if (remote_contact && empathy_chat_is_composing (chat))
887 icon_name = EMPATHY_IMAGE_TYPING;
889 else if (empathy_chat_is_sms_channel (chat))
891 icon_name = EMPATHY_IMAGE_SMS;
893 else if (remote_contact)
895 icon_name = empathy_icon_name_for_contact (remote_contact);
899 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
902 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
903 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
905 if (icon_name != NULL)
907 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
909 gtk_widget_show (tab_image);
910 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
912 gtk_widget_show (menu_image);
916 gtk_widget_hide (tab_image);
917 gtk_widget_hide (menu_image);
920 /* Update the sending spinner */
921 nb_sending = empathy_chat_get_n_messages_sending (chat);
922 sending_spinner = g_object_get_data (G_OBJECT (chat),
923 "chat-window-tab-sending-spinner");
925 g_object_set (sending_spinner,
926 "active", nb_sending > 0,
927 "visible", nb_sending > 0,
930 /* Update tab tooltip */
931 tooltip = g_string_new (NULL);
935 id = empathy_contact_get_id (remote_contact);
936 status = empathy_contact_get_presence_message (remote_contact);
943 if (empathy_chat_is_sms_channel (chat))
944 append_markup_printf (tooltip, "%s ", _("SMS:"));
946 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
947 id, tp_account_get_display_name (account));
951 char *tmp = g_strdup_printf (
952 ngettext ("Sending %d message",
953 "Sending %d messages",
957 g_string_append (tooltip, "\n");
958 g_string_append (tooltip, tmp);
960 gtk_widget_set_tooltip_text (sending_spinner, tmp);
964 if (!EMP_STR_EMPTY (status))
965 append_markup_printf (tooltip, "\n<i>%s</i>", status);
967 if (!EMP_STR_EMPTY (subject))
968 append_markup_printf (tooltip, "\n<b>%s</b> %s",
969 _("Topic:"), subject);
971 if (remote_contact && empathy_chat_is_composing (chat))
972 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
974 if (remote_contact != NULL)
976 const gchar * const *types;
978 types = empathy_contact_get_client_types (remote_contact);
979 if (empathy_client_types_contains_mobile_device ((GStrv) types))
981 /* I'm on a mobile device ! */
984 name = g_strdup_printf ("☎ %s", name);
989 markup = g_string_free (tooltip, FALSE);
990 widget = g_object_get_data (G_OBJECT (chat),
991 "chat-window-tab-tooltip-widget");
992 gtk_widget_set_tooltip_markup (widget, markup);
994 widget = g_object_get_data (G_OBJECT (chat),
995 "chat-window-menu-tooltip-widget");
996 gtk_widget_set_tooltip_markup (widget, markup);
999 /* Update tab and menu label */
1000 if (empathy_chat_is_highlighted (chat))
1002 markup = g_markup_printf_escaped (
1003 "<span color=\"red\" weight=\"bold\">%s</span>",
1008 markup = g_markup_escape_text (name, -1);
1011 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1012 gtk_label_set_markup (GTK_LABEL (widget), markup);
1013 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1014 gtk_label_set_markup (GTK_LABEL (widget), markup);
1017 /* Update the window if it's the current chat */
1018 if (self->priv->current_chat == chat)
1019 chat_window_update (self, update_contact_menu);
1025 chat_window_update_chat_tab (EmpathyChat *chat)
1027 chat_window_update_chat_tab_full (chat, TRUE);
1031 chat_window_chat_notify_cb (EmpathyChat *chat)
1033 EmpathyChatWindow *window;
1034 EmpathyContact *old_remote_contact;
1035 EmpathyContact *remote_contact = NULL;
1037 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1038 "chat-window-remote-contact");
1039 remote_contact = empathy_chat_get_remote_contact (chat);
1041 if (old_remote_contact != remote_contact)
1043 /* The remote-contact associated with the chat changed, we need
1044 * to keep track of any change of that contact and update the
1045 * window each time. */
1047 g_signal_connect_swapped (remote_contact, "notify",
1048 G_CALLBACK (chat_window_update_chat_tab), chat);
1050 if (old_remote_contact)
1051 g_signal_handlers_disconnect_by_func (old_remote_contact,
1052 chat_window_update_chat_tab, chat);
1054 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1055 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1058 chat_window_update_chat_tab (chat);
1060 window = chat_window_find_chat (chat);
1062 chat_window_update (window, FALSE);
1066 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1067 EmpathySmiley *smiley,
1070 EmpathyChatWindow *self = user_data;
1072 GtkTextBuffer *buffer;
1075 chat = self->priv->current_chat;
1077 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1078 gtk_text_buffer_get_end_iter (buffer, &iter);
1079 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1083 chat_window_conv_activate_cb (GtkAction *action,
1084 EmpathyChatWindow *self)
1088 EmpathyContact *remote_contact = NULL;
1090 /* Favorite room menu */
1091 is_room = empathy_chat_is_room (self->priv->current_chat);
1096 gboolean found = FALSE;
1097 EmpathyChatroom *chatroom;
1099 room = empathy_chat_get_id (self->priv->current_chat);
1100 account = empathy_chat_get_account (self->priv->current_chat);
1101 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1104 if (chatroom != NULL)
1105 found = empathy_chatroom_is_favorite (chatroom);
1107 DEBUG ("This room %s favorite", found ? "is" : "is not");
1108 gtk_toggle_action_set_active (
1109 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1111 if (chatroom != NULL)
1112 found = empathy_chatroom_is_always_urgent (chatroom);
1114 gtk_toggle_action_set_active (
1115 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1118 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1119 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1121 /* Show contacts menu */
1122 g_object_get (self->priv->current_chat,
1123 "remote-contact", &remote_contact,
1124 "show-contacts", &active,
1127 if (remote_contact == NULL)
1129 gtk_toggle_action_set_active (
1130 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1133 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1134 (remote_contact == NULL));
1136 if (remote_contact != NULL)
1137 g_object_unref (remote_contact);
1141 chat_window_clear_activate_cb (GtkAction *action,
1142 EmpathyChatWindow *self)
1144 empathy_chat_clear (self->priv->current_chat);
1148 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1149 EmpathyChatWindow *self)
1155 EmpathyChatroom *chatroom;
1157 active = gtk_toggle_action_get_active (toggle_action);
1158 account = empathy_chat_get_account (self->priv->current_chat);
1159 room = empathy_chat_get_id (self->priv->current_chat);
1160 name = empathy_chat_dup_name (self->priv->current_chat);
1162 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1163 account, room, name);
1165 empathy_chatroom_set_favorite (chatroom, active);
1166 g_object_unref (chatroom);
1171 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1172 EmpathyChatWindow *self)
1178 EmpathyChatroom *chatroom;
1180 active = gtk_toggle_action_get_active (toggle_action);
1181 account = empathy_chat_get_account (self->priv->current_chat);
1182 room = empathy_chat_get_id (self->priv->current_chat);
1183 name = empathy_chat_dup_name (self->priv->current_chat);
1185 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1186 account, room, name);
1188 empathy_chatroom_set_always_urgent (chatroom, active);
1189 g_object_unref (chatroom);
1194 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1195 EmpathyChatWindow *self)
1199 active = gtk_toggle_action_get_active (toggle_action);
1201 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1205 chat_window_invite_participant_activate_cb (GtkAction *action,
1206 EmpathyChatWindow *self)
1209 EmpathyTpChat *tp_chat;
1212 g_return_if_fail (self->priv->current_chat != NULL);
1214 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1216 dialog = empathy_invite_participant_dialog_new (
1217 GTK_WINDOW (self), tp_chat);
1219 gtk_widget_show (dialog);
1221 response = gtk_dialog_run (GTK_DIALOG (dialog));
1223 if (response == GTK_RESPONSE_ACCEPT)
1225 TpContact *tp_contact;
1226 EmpathyContact *contact;
1228 tp_contact = empathy_invite_participant_dialog_get_selected (
1229 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1230 if (tp_contact == NULL)
1233 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1235 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1237 g_object_unref (contact);
1241 gtk_widget_destroy (dialog);
1245 chat_window_close_activate_cb (GtkAction *action,
1246 EmpathyChatWindow *self)
1248 g_return_if_fail (self->priv->current_chat != NULL);
1250 maybe_close_chat (self, self->priv->current_chat);
1254 chat_window_edit_activate_cb (GtkAction *action,
1255 EmpathyChatWindow *self)
1257 GtkClipboard *clipboard;
1258 GtkTextBuffer *buffer;
1259 gboolean text_available;
1261 g_return_if_fail (self->priv->current_chat != NULL);
1263 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1265 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1266 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1267 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1271 buffer = gtk_text_view_get_buffer (
1272 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1274 if (gtk_text_buffer_get_has_selection (buffer))
1276 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1277 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1283 selection = empathy_theme_adium_get_has_selection (
1284 self->priv->current_chat->view);
1286 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1287 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1290 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1291 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1292 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1296 chat_window_cut_activate_cb (GtkAction *action,
1297 EmpathyChatWindow *self)
1299 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1301 empathy_chat_cut (self->priv->current_chat);
1305 chat_window_copy_activate_cb (GtkAction *action,
1306 EmpathyChatWindow *self)
1308 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1310 empathy_chat_copy (self->priv->current_chat);
1314 chat_window_paste_activate_cb (GtkAction *action,
1315 EmpathyChatWindow *self)
1317 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1319 empathy_chat_paste (self->priv->current_chat);
1323 chat_window_find_activate_cb (GtkAction *action,
1324 EmpathyChatWindow *self)
1326 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1328 empathy_chat_find (self->priv->current_chat);
1332 chat_window_tabs_next_activate_cb (GtkAction *action,
1333 EmpathyChatWindow *self)
1335 gint index_, numPages;
1336 gboolean wrap_around;
1338 g_object_get (gtk_settings_get_default (),
1339 "gtk-keynav-wrap-around", &wrap_around,
1342 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1343 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1345 if (index_ == (numPages - 1) && wrap_around)
1347 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1351 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1355 chat_window_tabs_previous_activate_cb (GtkAction *action,
1356 EmpathyChatWindow *self)
1358 gint index_, numPages;
1359 gboolean wrap_around;
1361 g_object_get (gtk_settings_get_default (),
1362 "gtk-keynav-wrap-around", &wrap_around,
1365 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1366 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1368 if (index_ <= 0 && wrap_around)
1370 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1375 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1379 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1380 EmpathyChatWindow *self)
1382 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1383 empathy_get_current_action_time ());
1387 chat_window_tabs_left_activate_cb (GtkAction *action,
1388 EmpathyChatWindow *self)
1391 gint index_, num_pages;
1393 chat = self->priv->current_chat;
1394 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1398 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1401 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1402 chat_window_menu_context_update (self, num_pages);
1406 chat_window_tabs_right_activate_cb (GtkAction *action,
1407 EmpathyChatWindow *self)
1410 gint index_, num_pages;
1412 chat = self->priv->current_chat;
1413 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1415 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1418 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1419 chat_window_menu_context_update (self, num_pages);
1422 static EmpathyChatWindow *
1423 empathy_chat_window_new (void)
1425 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1426 "default-width", 580,
1427 "default-height", 480,
1434 chat_window_detach_activate_cb (GtkAction *action,
1435 EmpathyChatWindow *self)
1437 EmpathyChatWindow *new_window;
1440 chat = self->priv->current_chat;
1441 new_window = empathy_chat_window_new ();
1443 empathy_chat_window_move_chat (self, new_window, chat);
1445 gtk_widget_show (GTK_WIDGET (new_window));
1449 chat_window_help_contents_activate_cb (GtkAction *action,
1450 EmpathyChatWindow *self)
1452 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1456 chat_window_help_about_activate_cb (GtkAction *action,
1457 EmpathyChatWindow *self)
1459 empathy_about_dialog_new (GTK_WINDOW (self));
1463 chat_window_delete_event_cb (GtkWidget *dialog,
1465 EmpathyChatWindow *self)
1467 EmpathyChat *chat = NULL;
1471 DEBUG ("Delete event received");
1473 for (l = self->priv->chats; l != NULL; l = l->next)
1475 if (chat_needs_close_confirmation (l->data))
1484 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1488 remove_all_chats (self);
1495 chat_window_composing_cb (EmpathyChat *chat,
1496 gboolean is_composing,
1497 EmpathyChatWindow *self)
1499 chat_window_update_chat_tab (chat);
1503 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1506 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1510 chat_window_notification_closed_cb (NotifyNotification *notify,
1511 EmpathyChatWindow *self)
1513 g_object_unref (notify);
1514 if (self->priv->notification == notify)
1515 self->priv->notification = NULL;
1519 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1520 EmpathyMessage *message,
1523 EmpathyContact *sender;
1524 const gchar *header;
1528 gboolean res, has_x_canonical_append;
1529 NotifyNotification *notification = self->priv->notification;
1531 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1534 res = g_settings_get_boolean (self->priv->gsettings_notif,
1535 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1540 sender = empathy_message_get_sender (message);
1541 header = empathy_contact_get_alias (sender);
1542 body = empathy_message_get_body (message);
1543 escaped = g_markup_escape_text (body, -1);
1545 has_x_canonical_append = empathy_notify_manager_has_capability (
1546 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1548 if (notification != NULL && !has_x_canonical_append)
1550 /* if the notification server supports x-canonical-append, it is
1551 better to not use notify_notification_update to avoid
1552 overwriting the current notification message */
1553 notify_notification_update (notification,
1554 header, escaped, NULL);
1558 /* if the notification server supports x-canonical-append,
1559 the hint will be added, so that the message from the
1560 just created notification will be automatically appended
1561 to an existing notification with the same title.
1562 In this way the previous message will not be lost: the new
1563 message will appear below it, in the same notification */
1564 const gchar *category = empathy_chat_is_room (chat)
1565 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1566 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1568 notification = empathy_notify_manager_create_notification (header,
1571 if (self->priv->notification == NULL)
1572 self->priv->notification = notification;
1574 tp_g_signal_connect_object (notification, "closed",
1575 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1577 if (has_x_canonical_append)
1579 /* We have to set a not empty string to keep libnotify happy */
1580 notify_notification_set_hint_string (notification,
1581 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1584 notify_notification_set_hint (notification,
1585 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1588 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1589 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1593 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1594 g_object_unref (pixbuf);
1597 notify_notification_show (notification, NULL);
1603 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1607 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1609 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1615 chat_window_new_message_cb (EmpathyChat *chat,
1616 EmpathyMessage *message,
1618 gboolean should_highlight,
1619 EmpathyChatWindow *self)
1622 gboolean needs_urgency;
1623 EmpathyContact *sender;
1625 has_focus = empathy_chat_window_has_focus (self);
1627 /* - if we're the sender, we play the sound if it's specified in the
1628 * preferences and we're not away.
1629 * - if we receive a message, we play the sound if it's specified in the
1630 * preferences and the window does not have focus on the chat receiving
1634 sender = empathy_message_get_sender (message);
1636 if (empathy_contact_is_user (sender))
1638 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1639 EMPATHY_SOUND_MESSAGE_OUTGOING);
1643 if (has_focus && self->priv->current_chat == chat)
1645 /* window and tab are focused so consider the message to be read */
1647 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1648 empathy_chat_messages_read (chat);
1652 /* Update the chat tab if this is the first unread message */
1653 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1655 chat_window_update_chat_tab (chat);
1658 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1659 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1660 * an unamed MUC (msn-like).
1661 * In case of a MUC, we set urgency if either:
1662 * a) the chatroom's always_urgent property is TRUE
1663 * b) the message contains our alias
1665 if (empathy_chat_is_room (chat))
1669 EmpathyChatroom *chatroom;
1671 account = empathy_chat_get_account (chat);
1672 room = empathy_chat_get_id (chat);
1674 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1677 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1678 needs_urgency = TRUE;
1680 needs_urgency = should_highlight;
1684 needs_urgency = TRUE;
1690 chat_window_set_urgency_hint (self, TRUE);
1692 /* Pending messages have already been displayed and notified in the
1693 * approver, so we don't display a notification and play a sound
1697 empathy_sound_manager_play (self->priv->sound_mgr,
1698 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1700 chat_window_show_or_update_notification (self, message, chat);
1704 /* update the number of unread messages and the window icon */
1705 chat_window_title_update (self);
1706 chat_window_icon_update (self, TRUE);
1710 chat_window_command_part (EmpathyChat *chat,
1713 EmpathyChat *chat_to_be_parted;
1714 EmpathyTpChat *tp_chat = NULL;
1716 if (strv[1] == NULL)
1718 /* No chatroom ID specified */
1719 tp_chat = empathy_chat_get_tp_chat (chat);
1722 empathy_tp_chat_leave (tp_chat, "");
1727 chat_to_be_parted = empathy_chat_window_find_chat (
1728 empathy_chat_get_account (chat), strv[1], FALSE);
1730 if (chat_to_be_parted != NULL)
1732 /* Found a chatroom matching the specified ID */
1733 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1736 empathy_tp_chat_leave (tp_chat, strv[2]);
1742 /* Going by the syntax of PART command:
1744 * /PART [<chatroom-ID>] [<reason>]
1746 * Chatroom-ID is not a must to specify a reason.
1747 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1748 * MUC then the current chatroom should be parted and srtv[1] should
1749 * be treated as part of the optional part-message. */
1750 message = g_strconcat (strv[1], " ", strv[2], NULL);
1751 tp_chat = empathy_chat_get_tp_chat (chat);
1754 empathy_tp_chat_leave (tp_chat, message);
1760 static GtkNotebook *
1761 notebook_create_window_cb (GtkNotebook *source,
1767 EmpathyChatWindow *window, *new_window;
1770 chat = EMPATHY_CHAT (page);
1771 window = chat_window_find_chat (chat);
1773 new_window = empathy_chat_window_new ();
1775 DEBUG ("Detach hook called");
1777 empathy_chat_window_move_chat (window, new_window, chat);
1779 gtk_widget_show (GTK_WIDGET (new_window));
1780 gtk_window_move (GTK_WINDOW (new_window), x, y);
1786 chat_window_page_switched_cb (GtkNotebook *notebook,
1789 EmpathyChatWindow *self)
1791 EmpathyChat *chat = EMPATHY_CHAT (child);
1793 DEBUG ("Page switched");
1795 if (self->priv->page_added)
1797 self->priv->page_added = FALSE;
1798 empathy_chat_scroll_down (chat);
1800 else if (self->priv->current_chat == chat)
1805 self->priv->current_chat = chat;
1806 empathy_chat_messages_read (chat);
1808 chat_window_update_chat_tab (chat);
1812 chat_window_page_added_cb (GtkNotebook *notebook,
1815 EmpathyChatWindow *self)
1819 /* If we just received DND to the same window, we don't want
1820 * to do anything here like removing the tab and then readding
1821 * it, so we return here and in "page-added".
1823 if (self->priv->dnd_same_window)
1825 DEBUG ("Page added (back to the same window)");
1826 self->priv->dnd_same_window = FALSE;
1830 DEBUG ("Page added");
1832 /* Get chat object */
1833 chat = EMPATHY_CHAT (child);
1835 /* Connect chat signals for this window */
1836 g_signal_connect (chat, "composing",
1837 G_CALLBACK (chat_window_composing_cb), self);
1838 g_signal_connect (chat, "new-message",
1839 G_CALLBACK (chat_window_new_message_cb), self);
1840 g_signal_connect (chat, "part-command-entered",
1841 G_CALLBACK (chat_window_command_part), NULL);
1842 g_signal_connect (chat, "notify::tp-chat",
1843 G_CALLBACK (chat_window_update_chat_tab), self);
1845 /* Set flag so we know to perform some special operations on
1846 * switch page due to the new page being added.
1848 self->priv->page_added = TRUE;
1850 /* Get list of chats up to date */
1851 self->priv->chats = g_list_append (self->priv->chats, chat);
1853 chat_window_update_chat_tab (chat);
1857 chat_window_page_removed_cb (GtkNotebook *notebook,
1860 EmpathyChatWindow *self)
1864 /* If we just received DND to the same window, we don't want
1865 * to do anything here like removing the tab and then readding
1866 * it, so we return here and in "page-added".
1868 if (self->priv->dnd_same_window)
1870 DEBUG ("Page removed (and will be readded to same window)");
1874 DEBUG ("Page removed");
1876 /* Get chat object */
1877 chat = EMPATHY_CHAT (child);
1879 /* Disconnect all signal handlers for this chat and this window */
1880 g_signal_handlers_disconnect_by_func (chat,
1881 G_CALLBACK (chat_window_composing_cb), self);
1882 g_signal_handlers_disconnect_by_func (chat,
1883 G_CALLBACK (chat_window_new_message_cb), self);
1884 g_signal_handlers_disconnect_by_func (chat,
1885 G_CALLBACK (chat_window_update_chat_tab), self);
1887 /* Keep list of chats up to date */
1888 self->priv->chats = g_list_remove (self->priv->chats, chat);
1889 empathy_chat_messages_read (chat);
1891 if (self->priv->chats == NULL)
1893 gtk_widget_destroy (GTK_WIDGET (self));
1897 chat_window_update (self, TRUE);
1902 chat_window_focus_in_event_cb (GtkWidget *widget,
1904 EmpathyChatWindow *self)
1906 empathy_chat_messages_read (self->priv->current_chat);
1908 chat_window_set_urgency_hint (self, FALSE);
1910 /* Update the title, since we now mark all unread messages as read. */
1911 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1917 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1918 EmpathyChatWindow *self)
1920 chat_window_contact_menu_update (self);
1924 chat_window_focus_out_event_cb (GtkWidget *widget,
1926 EmpathyChatWindow *self)
1928 if (self->priv->individual_mgr != NULL)
1931 /* Keep the individual manager alive so we won't fetch everything from Folks
1932 * each time we need to use it. Loading FolksAggregator can takes quite a
1933 * while (if user has a huge LDAP abook for example) and it blocks
1934 * the mainloop during most of this loading. We workaround this by loading
1935 * it when the chat window has been unfocused and so, hopefully, not impact
1936 * the reactivity of the chat window too much.
1938 * The individual manager (and so Folks) is needed to know to which
1939 * FolksIndividual a TpContact belongs, including:
1940 * - empathy_chat_get_contact_menu: to list all the personas of the contact
1941 * - empathy_display_individual_info: to invoke gnome-contacts with the
1942 * FolksIndividual.id of the contact
1943 * - drag_data_received_individual_id: to find the individual associated
1944 * with the ID we received from the DnD in order to invite him.
1946 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
1948 if (!empathy_individual_manager_get_contacts_loaded (
1949 self->priv->individual_mgr))
1951 /* We want to update the contact menu when Folks is loaded so we can
1952 * list all the personas of the contact. */
1953 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
1954 G_CALLBACK (contacts_loaded_cb), self, 0);
1957 g_object_notify (G_OBJECT (self), "individual-manager");
1963 chat_window_drag_drop (GtkWidget *widget,
1964 GdkDragContext *context,
1968 EmpathyChatWindow *self)
1972 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1973 if (target == GDK_NONE)
1974 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1976 if (target != GDK_NONE)
1978 gtk_drag_get_data (widget, context, target, time_);
1986 chat_window_drag_motion (GtkWidget *widget,
1987 GdkDragContext *context,
1991 EmpathyChatWindow *self)
1995 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1997 if (target != GDK_NONE)
1999 /* This is a file drag. Ensure the contact is online and set the
2000 drag type to COPY. Note that it's possible that the tab will
2001 be switched by GTK+ after a timeout from drag_motion without
2002 getting another drag_motion to disable the drop. You have
2003 to hold your mouse really still.
2005 EmpathyContact *contact;
2007 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2009 /* contact is NULL for multi-user chats. We don't do
2010 * file transfers to MUCs. We also don't send files
2011 * to offline contacts or contacts that don't support
2014 if ((contact == NULL) || !empathy_contact_is_online (contact))
2016 gdk_drag_status (context, 0, time_);
2020 if (!(empathy_contact_get_capabilities (contact)
2021 & EMPATHY_CAPABILITIES_FT))
2023 gdk_drag_status (context, 0, time_);
2027 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2031 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2032 if (target != GDK_NONE)
2034 /* This is a drag of a contact from a contact list. Set to COPY.
2035 FIXME: If this drag is to a MUC window, it invites the user.
2036 Otherwise, it opens a chat. Should we use a different drag
2037 type for invites? Should we allow ASK?
2039 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2047 drag_data_received_individual_id (EmpathyChatWindow *self,
2049 GdkDragContext *context,
2052 GtkSelectionData *selection,
2057 FolksIndividual *individual;
2058 EmpathyTpChat *chat;
2059 TpContact *tp_contact;
2061 EmpathyContact *contact;
2063 id = (const gchar *) gtk_selection_data_get_data (selection);
2065 DEBUG ("DND invididual %s", id);
2067 if (self->priv->current_chat == NULL)
2070 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2074 if (!empathy_tp_chat_can_add_contact (chat))
2076 DEBUG ("Can't invite contact to %s",
2077 tp_proxy_get_object_path (chat));
2081 if (self->priv->individual_mgr == NULL)
2082 /* Not likely as we have to focus out the chat window in order to start
2083 * the DnD but best to be safe. */
2086 individual = empathy_individual_manager_lookup_member (
2087 self->priv->individual_mgr, id);
2088 if (individual == NULL)
2090 DEBUG ("Failed to find individual %s", id);
2094 conn = tp_channel_get_connection ((TpChannel *) chat);
2095 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2096 if (tp_contact == NULL)
2098 DEBUG ("Can't find a TpContact on connection %s for %s",
2099 tp_proxy_get_object_path (conn), id);
2103 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2104 tp_channel_get_identifier ((TpChannel *) chat));
2106 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2107 empathy_tp_chat_add (chat, contact, NULL);
2108 g_object_unref (contact);
2111 gtk_drag_finish (context, TRUE, FALSE, time_);
2115 chat_window_drag_data_received (GtkWidget *widget,
2116 GdkDragContext *context,
2119 GtkSelectionData *selection,
2122 EmpathyChatWindow *self)
2124 if (info == DND_DRAG_TYPE_CONTACT_ID)
2126 EmpathyChat *chat = NULL;
2127 EmpathyChatWindow *old_window;
2128 TpAccount *account = NULL;
2129 EmpathyClientFactory *factory;
2132 const gchar *account_id;
2133 const gchar *contact_id;
2135 id = (const gchar*) gtk_selection_data_get_data (selection);
2137 factory = empathy_client_factory_dup ();
2139 DEBUG ("DND contact from roster with id:'%s'", id);
2141 strv = g_strsplit (id, ":", 2);
2142 if (g_strv_length (strv) == 2)
2144 account_id = strv[0];
2145 contact_id = strv[1];
2147 account = tp_simple_client_factory_ensure_account (
2148 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2150 g_object_unref (factory);
2151 if (account != NULL)
2152 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2155 if (account == NULL)
2158 gtk_drag_finish (context, FALSE, FALSE, time_);
2164 empathy_chat_with_contact_id (account, contact_id,
2165 empathy_get_current_action_time (), NULL, NULL);
2173 old_window = chat_window_find_chat (chat);
2176 if (old_window == self)
2178 gtk_drag_finish (context, TRUE, FALSE, time_);
2182 empathy_chat_window_move_chat (old_window, self, chat);
2186 empathy_chat_window_add_chat (self, chat);
2189 /* Added to take care of any outstanding chat events */
2190 empathy_chat_window_present_chat (chat,
2191 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2193 /* We should return TRUE to remove the data when doing
2194 * GDK_ACTION_MOVE, but we don't here otherwise it has
2195 * weird consequences, and we handle that internally
2196 * anyway with add_chat () and remove_chat ().
2198 gtk_drag_finish (context, TRUE, FALSE, time_);
2200 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2202 drag_data_received_individual_id (self, widget, context, x, y,
2203 selection, info, time_);
2205 else if (info == DND_DRAG_TYPE_URI_LIST)
2207 EmpathyContact *contact;
2210 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2212 /* contact is NULL when current_chat is a multi-user chat.
2213 * We don't do file transfers to MUCs, so just cancel the drag.
2215 if (contact == NULL)
2217 gtk_drag_finish (context, TRUE, FALSE, time_);
2221 data = (const gchar *) gtk_selection_data_get_data (selection);
2222 empathy_send_file_from_uri_list (contact, data);
2224 gtk_drag_finish (context, TRUE, FALSE, time_);
2226 else if (info == DND_DRAG_TYPE_TAB)
2229 EmpathyChatWindow *old_window = NULL;
2233 chat = (void *) gtk_selection_data_get_data (selection);
2234 old_window = chat_window_find_chat (*chat);
2238 self->priv->dnd_same_window = (old_window == self);
2240 DEBUG ("DND tab (within same window: %s)",
2241 self->priv->dnd_same_window ? "Yes" : "No");
2246 DEBUG ("DND from unknown source");
2247 gtk_drag_finish (context, FALSE, FALSE, time_);
2252 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2253 guint num_chats_in_manager,
2254 EmpathyChatWindow *self)
2256 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2257 num_chats_in_manager > 0);
2261 chat_window_finalize (GObject *object)
2263 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2265 DEBUG ("Finalized: %p", object);
2267 g_object_unref (self->priv->ui_manager);
2268 g_object_unref (self->priv->chatroom_manager);
2269 g_object_unref (self->priv->notify_mgr);
2270 g_object_unref (self->priv->gsettings_chat);
2271 g_object_unref (self->priv->gsettings_notif);
2272 g_object_unref (self->priv->gsettings_ui);
2273 g_object_unref (self->priv->sound_mgr);
2274 g_clear_object (&self->priv->individual_mgr);
2276 if (self->priv->notification != NULL)
2278 notify_notification_close (self->priv->notification, NULL);
2279 self->priv->notification = NULL;
2282 if (self->priv->contact_targets)
2283 gtk_target_list_unref (self->priv->contact_targets);
2285 if (self->priv->file_targets)
2286 gtk_target_list_unref (self->priv->file_targets);
2288 if (self->priv->chat_manager)
2290 g_signal_handler_disconnect (self->priv->chat_manager,
2291 self->priv->chat_manager_chats_changed_id);
2292 g_object_unref (self->priv->chat_manager);
2293 self->priv->chat_manager = NULL;
2296 chat_windows = g_list_remove (chat_windows, self);
2298 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2302 chat_window_get_property (GObject *object,
2307 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2309 switch (property_id)
2311 case PROP_INDIVIDUAL_MGR:
2312 g_value_set_object (value, self->priv->individual_mgr);
2314 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2320 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2322 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2325 object_class->get_property = chat_window_get_property;
2326 object_class->finalize = chat_window_finalize;
2328 spec = g_param_spec_object ("individual-manager", "individual-manager",
2329 "EmpathyIndividualManager",
2330 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2331 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2332 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2334 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2338 empathy_chat_window_init (EmpathyChatWindow *self)
2341 GtkAccelGroup *accel_group;
2346 GtkWidget *chat_vbox;
2348 EmpathySmileyManager *smiley_manager;
2350 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2351 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2353 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2354 gui = empathy_builder_get_file (filename,
2355 "chat_vbox", &chat_vbox,
2356 "ui_manager", &self->priv->ui_manager,
2357 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2358 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2359 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2360 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2361 "menu_edit_cut", &self->priv->menu_edit_cut,
2362 "menu_edit_copy", &self->priv->menu_edit_copy,
2363 "menu_edit_paste", &self->priv->menu_edit_paste,
2364 "menu_edit_find", &self->priv->menu_edit_find,
2365 "menu_tabs_next", &self->priv->menu_tabs_next,
2366 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2367 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2368 "menu_tabs_left", &self->priv->menu_tabs_left,
2369 "menu_tabs_right", &self->priv->menu_tabs_right,
2370 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2374 empathy_builder_connect (gui, self,
2375 "menu_conv", "activate", chat_window_conv_activate_cb,
2376 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2377 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2378 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2379 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2380 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2381 "menu_conv_close", "activate", chat_window_close_activate_cb,
2382 "menu_edit", "activate", chat_window_edit_activate_cb,
2383 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2384 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2385 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2386 "menu_edit_find", "activate", chat_window_find_activate_cb,
2387 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2388 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2389 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2390 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2391 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2392 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2393 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2394 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2397 empathy_set_css_provider (GTK_WIDGET (self));
2399 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2400 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2401 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2402 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2404 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2406 self->priv->notebook = gtk_notebook_new ();
2408 g_signal_connect (self->priv->notebook, "create-window",
2409 G_CALLBACK (notebook_create_window_cb), self);
2411 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2413 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2414 "EmpathyChatWindow");
2415 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2416 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2417 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2418 gtk_widget_show (self->priv->notebook);
2421 accel_group = gtk_accel_group_new ();
2422 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2424 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2426 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2429 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2433 g_object_unref (accel_group);
2435 /* Set up drag target lists */
2436 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2437 G_N_ELEMENTS (drag_types_dest_contact));
2439 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2440 G_N_ELEMENTS (drag_types_dest_file));
2442 /* Set up smiley menu */
2443 smiley_manager = empathy_smiley_manager_dup_singleton ();
2444 submenu = empathy_smiley_menu_new (smiley_manager,
2445 chat_window_insert_smiley_activate_cb, self);
2447 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2448 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2449 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2450 g_object_unref (smiley_manager);
2452 /* Set up signals we can't do with ui file since we may need to
2453 * block/unblock them at some later stage.
2456 g_signal_connect (self, "delete_event",
2457 G_CALLBACK (chat_window_delete_event_cb), self);
2458 g_signal_connect (self, "focus_in_event",
2459 G_CALLBACK (chat_window_focus_in_event_cb), self);
2460 g_signal_connect (self, "focus_out_event",
2461 G_CALLBACK (chat_window_focus_out_event_cb), self);
2462 g_signal_connect_after (self->priv->notebook, "switch_page",
2463 G_CALLBACK (chat_window_page_switched_cb), self);
2464 g_signal_connect (self->priv->notebook, "page_added",
2465 G_CALLBACK (chat_window_page_added_cb), self);
2466 g_signal_connect (self->priv->notebook, "page_removed",
2467 G_CALLBACK (chat_window_page_removed_cb), self);
2469 /* Set up drag and drop */
2470 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2471 GTK_DEST_DEFAULT_HIGHLIGHT,
2473 G_N_ELEMENTS (drag_types_dest),
2474 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2476 /* connect_after to allow GtkNotebook's built-in tab switching */
2477 g_signal_connect_after (self->priv->notebook, "drag-motion",
2478 G_CALLBACK (chat_window_drag_motion), self);
2479 g_signal_connect (self->priv->notebook, "drag-data-received",
2480 G_CALLBACK (chat_window_drag_data_received), self);
2481 g_signal_connect (self->priv->notebook, "drag-drop",
2482 G_CALLBACK (chat_window_drag_drop), self);
2484 chat_windows = g_list_prepend (chat_windows, self);
2486 /* Set up private details */
2487 self->priv->chats = NULL;
2488 self->priv->current_chat = NULL;
2489 self->priv->notification = NULL;
2491 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2493 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2494 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2495 self->priv->chat_manager, "closed-chats-changed",
2496 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2498 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2499 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2501 g_object_ref (self->priv->ui_manager);
2502 g_object_unref (gui);
2505 /* Returns the window to open a new tab in if there is a suitable window,
2506 * otherwise, returns NULL indicating that a new window should be added.
2508 static EmpathyChatWindow *
2509 empathy_chat_window_get_default (gboolean room)
2511 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2513 gboolean separate_windows = TRUE;
2515 separate_windows = g_settings_get_boolean (gsettings,
2516 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2518 g_object_unref (gsettings);
2520 if (separate_windows)
2521 /* Always create a new window */
2524 for (l = chat_windows; l; l = l->next)
2526 EmpathyChatWindow *chat_window;
2527 guint nb_rooms, nb_private;
2529 chat_window = l->data;
2531 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2533 /* Skip the window if there aren't any rooms in it */
2534 if (room && nb_rooms == 0)
2537 /* Skip the window if there aren't any 1-1 chats in it */
2538 if (!room && nb_private == 0)
2548 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2552 GtkWidget *popup_label;
2554 GValue value = { 0, };
2556 g_return_if_fail (self != NULL);
2557 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2559 /* Reference the chat object */
2560 g_object_ref (chat);
2562 /* If this window has just been created, position it */
2563 if (self->priv->chats == NULL)
2565 const gchar *name = "chat-window";
2566 gboolean separate_windows;
2568 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2569 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2571 if (empathy_chat_is_room (chat))
2572 name = "room-window";
2574 if (separate_windows)
2578 /* Save current position of the window */
2579 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2581 /* First bind to the 'generic' name. So new window for which we didn't
2582 * save a geometry yet will have the geometry of the last saved
2583 * window (bgo #601191). */
2584 empathy_geometry_bind (GTK_WINDOW (self), name);
2586 /* Restore previous position of the window so the newly created window
2587 * won't be in the same position as the latest saved window and so
2588 * completely hide it. */
2589 gtk_window_move (GTK_WINDOW (self), x, y);
2591 /* Then bind it to the name of the contact/room so we'll save the
2592 * geometry specific to this window */
2593 name = empathy_chat_get_id (chat);
2596 empathy_geometry_bind (GTK_WINDOW (self), name);
2599 child = GTK_WIDGET (chat);
2600 label = chat_window_create_label (self, chat, TRUE);
2601 popup_label = chat_window_create_label (self, chat, FALSE);
2602 gtk_widget_show (child);
2604 g_signal_connect (chat, "notify::name",
2605 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2606 g_signal_connect (chat, "notify::subject",
2607 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2608 g_signal_connect (chat, "notify::remote-contact",
2609 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2610 g_signal_connect (chat, "notify::sms-channel",
2611 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2612 g_signal_connect (chat, "notify::n-messages-sending",
2613 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2614 g_signal_connect (chat, "notify::nb-unread-messages",
2615 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2616 chat_window_chat_notify_cb (chat);
2618 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2620 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2621 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2622 g_value_init (&value, G_TYPE_BOOLEAN);
2623 g_value_set_boolean (&value, TRUE);
2624 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2625 child, "tab-expand" , &value);
2626 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2627 child, "tab-fill" , &value);
2628 g_value_unset (&value);
2630 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2634 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2638 EmpathyContact *remote_contact;
2639 EmpathyChatManager *chat_manager;
2641 g_return_if_fail (self != NULL);
2642 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2644 g_signal_handlers_disconnect_by_func (chat,
2645 chat_window_chat_notify_cb, NULL);
2647 remote_contact = g_object_get_data (G_OBJECT (chat),
2648 "chat-window-remote-contact");
2652 g_signal_handlers_disconnect_by_func (remote_contact,
2653 chat_window_update_chat_tab, chat);
2656 chat_manager = empathy_chat_manager_dup_singleton ();
2657 empathy_chat_manager_closed_chat (chat_manager, chat);
2658 g_object_unref (chat_manager);
2660 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2662 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2664 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2666 g_object_unref (chat);
2670 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2671 EmpathyChatWindow *new_window,
2676 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2677 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2678 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2680 widget = GTK_WIDGET (chat);
2682 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2683 G_OBJECT (widget)->ref_count);
2685 /* We reference here to make sure we don't loose the widget
2686 * and the EmpathyChat object during the move.
2688 g_object_ref (chat);
2689 g_object_ref (widget);
2691 empathy_chat_window_remove_chat (old_window, chat);
2692 empathy_chat_window_add_chat (new_window, chat);
2694 g_object_unref (widget);
2695 g_object_unref (chat);
2699 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2704 g_return_if_fail (self != NULL);
2705 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2707 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2710 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2715 empathy_chat_window_find_chat (TpAccount *account,
2717 gboolean sms_channel)
2721 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2723 for (l = chat_windows; l; l = l->next)
2725 EmpathyChatWindow *window = l->data;
2728 for (ll = window->priv->chats; ll; ll = ll->next)
2734 if (account == empathy_chat_get_account (chat) &&
2735 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2736 sms_channel == empathy_chat_is_sms_channel (chat))
2745 empathy_chat_window_present_chat (EmpathyChat *chat,
2748 EmpathyChatWindow *self;
2749 guint32 x_timestamp;
2751 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2753 self = chat_window_find_chat (chat);
2755 /* If the chat has no window, create one */
2758 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2761 self = empathy_chat_window_new ();
2763 /* we want to display the newly created window even if we
2764 * don't present it */
2765 gtk_widget_show (GTK_WIDGET (self));
2768 empathy_chat_window_add_chat (self, chat);
2771 /* Don't force the window to show itself when it wasn't
2772 * an action by the user
2774 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2777 if (x_timestamp != GDK_CURRENT_TIME)
2779 /* Don't present or switch tab if the action was earlier than the
2780 * last actions X time, accounting for overflow and the first ever
2783 if (self->priv->x_user_action_time != 0
2784 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2787 self->priv->x_user_action_time = x_timestamp;
2790 empathy_chat_window_switch_to_chat (self, chat);
2792 /* Don't use empathy_window_present_with_time () which would move the window
2793 * to our current desktop but move to the window's desktop instead. This is
2794 * more coherent with Shell's 'app is ready' notication which moves the view
2795 * to the app desktop rather than moving the app itself. */
2796 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2798 gtk_widget_grab_focus (chat->input_text_view);
2803 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2808 guint _nb_rooms = 0, _nb_private = 0;
2810 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2812 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2818 if (nb_rooms != NULL)
2819 *nb_rooms = _nb_rooms;
2820 if (nb_private != NULL)
2821 *nb_private = _nb_private;
2824 EmpathyIndividualManager *
2825 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2827 return self->priv->individual_mgr;