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"
31 #include <glib/gi18n.h>
33 #include "empathy-about-dialog.h"
34 #include "empathy-chat-manager.h"
35 #include "empathy-chatroom-manager.h"
36 #include "empathy-client-factory.h"
37 #include "empathy-geometry.h"
38 #include "empathy-gsettings.h"
39 #include "empathy-images.h"
40 #include "empathy-invite-participant-dialog.h"
41 #include "empathy-notify-manager.h"
42 #include "empathy-request-util.h"
43 #include "empathy-smiley-manager.h"
44 #include "empathy-sound-manager.h"
45 #include "empathy-ui-utils.h"
46 #include "empathy-utils.h"
48 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
49 #include "empathy-debug.h"
51 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
53 #define X_EARLIER_OR_EQL(t1, t2) \
54 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
55 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
60 PROP_INDIVIDUAL_MGR = 1
63 struct _EmpathyChatWindowPriv
65 EmpathyChat *current_chat;
68 gboolean dnd_same_window;
69 EmpathyChatroomManager *chatroom_manager;
70 EmpathyNotifyManager *notify_mgr;
71 EmpathyIndividualManager *individual_mgr;
73 NotifyNotification *notification;
75 GtkTargetList *contact_targets;
76 GtkTargetList *file_targets;
78 EmpathyChatManager *chat_manager;
79 gulong chat_manager_chats_changed_id;
82 GtkUIManager *ui_manager;
83 GtkAction *menu_conv_insert_smiley;
84 GtkAction *menu_conv_favorite;
85 GtkAction *menu_conv_join_chat;
86 GtkAction *menu_conv_leave_chat;
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;
1089 gboolean disconnected;
1091 /* Favorite room menu */
1092 is_room = empathy_chat_is_room (self->priv->current_chat);
1097 gboolean found = FALSE;
1098 EmpathyChatroom *chatroom;
1100 room = empathy_chat_get_id (self->priv->current_chat);
1101 account = empathy_chat_get_account (self->priv->current_chat);
1102 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1105 if (chatroom != NULL)
1106 found = empathy_chatroom_is_favorite (chatroom);
1108 DEBUG ("This room %s favorite", found ? "is" : "is not");
1109 gtk_toggle_action_set_active (
1110 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1112 if (chatroom != NULL)
1113 found = empathy_chatroom_is_always_urgent (chatroom);
1115 gtk_toggle_action_set_active (
1116 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1119 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1120 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1122 /* Show contacts menu */
1123 g_object_get (self->priv->current_chat,
1124 "remote-contact", &remote_contact,
1125 "show-contacts", &active,
1128 if (remote_contact == NULL)
1130 gtk_toggle_action_set_active (
1131 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1134 /* Menu-items to be visible for MUCs only */
1135 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1136 (remote_contact == NULL));
1138 disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1141 gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1142 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1146 TpChannel *channel = NULL;
1147 TpContact *self_contact = NULL;
1148 TpHandle self_handle = 0;
1150 channel = (TpChannel *) (empathy_chat_get_tp_chat (
1151 self->priv->current_chat));
1152 self_contact = tp_channel_group_get_self_contact (channel);
1153 if (self_contact == NULL)
1155 /* The channel may not be a group */
1156 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1160 self_handle = tp_contact_get_handle (self_contact);
1161 /* There is sometimes a lag between the members-changed signal
1162 emitted on tp-chat and invalidated signal being emitted on the channel.
1163 Leave Chat menu-item should be sensitive only till our self-handle is
1164 a part of channel-members */
1165 gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1169 /* Join Chat is insensitive for a connected chat */
1170 gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1173 if (remote_contact != NULL)
1174 g_object_unref (remote_contact);
1178 chat_window_clear_activate_cb (GtkAction *action,
1179 EmpathyChatWindow *self)
1181 empathy_chat_clear (self->priv->current_chat);
1185 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1186 EmpathyChatWindow *self)
1192 EmpathyChatroom *chatroom;
1194 active = gtk_toggle_action_get_active (toggle_action);
1195 account = empathy_chat_get_account (self->priv->current_chat);
1196 room = empathy_chat_get_id (self->priv->current_chat);
1197 name = empathy_chat_dup_name (self->priv->current_chat);
1199 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1200 account, room, name);
1202 empathy_chatroom_set_favorite (chatroom, active);
1203 g_object_unref (chatroom);
1208 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1209 EmpathyChatWindow *self)
1215 EmpathyChatroom *chatroom;
1217 active = gtk_toggle_action_get_active (toggle_action);
1218 account = empathy_chat_get_account (self->priv->current_chat);
1219 room = empathy_chat_get_id (self->priv->current_chat);
1220 name = empathy_chat_dup_name (self->priv->current_chat);
1222 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1223 account, room, name);
1225 empathy_chatroom_set_always_urgent (chatroom, active);
1226 g_object_unref (chatroom);
1231 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1232 EmpathyChatWindow *self)
1236 active = gtk_toggle_action_get_active (toggle_action);
1238 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1242 chat_window_invite_participant_activate_cb (GtkAction *action,
1243 EmpathyChatWindow *self)
1246 EmpathyTpChat *tp_chat;
1249 g_return_if_fail (self->priv->current_chat != NULL);
1251 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1253 dialog = empathy_invite_participant_dialog_new (
1254 GTK_WINDOW (self), tp_chat);
1256 gtk_widget_show (dialog);
1258 response = gtk_dialog_run (GTK_DIALOG (dialog));
1260 if (response == GTK_RESPONSE_ACCEPT)
1262 TpContact *tp_contact;
1263 EmpathyContact *contact;
1265 tp_contact = empathy_invite_participant_dialog_get_selected (
1266 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1267 if (tp_contact == NULL)
1270 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1272 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1274 g_object_unref (contact);
1278 gtk_widget_destroy (dialog);
1282 chat_window_join_chat_activate_cb (GtkAction *action,
1283 EmpathyChatWindow *self)
1285 g_return_if_fail (self->priv->current_chat != NULL);
1287 empathy_chat_join_muc (self->priv->current_chat,
1288 empathy_chat_get_id (self->priv->current_chat));
1292 chat_window_leave_chat_activate_cb (GtkAction *action,
1293 EmpathyChatWindow *self)
1295 EmpathyTpChat * tp_chat;
1297 g_return_if_fail (self->priv->current_chat != NULL);
1299 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1300 if (tp_chat != NULL)
1301 empathy_tp_chat_leave (tp_chat, "");
1305 chat_window_close_activate_cb (GtkAction *action,
1306 EmpathyChatWindow *self)
1308 g_return_if_fail (self->priv->current_chat != NULL);
1310 maybe_close_chat (self, self->priv->current_chat);
1314 chat_window_edit_activate_cb (GtkAction *action,
1315 EmpathyChatWindow *self)
1317 GtkClipboard *clipboard;
1318 GtkTextBuffer *buffer;
1319 gboolean text_available;
1321 g_return_if_fail (self->priv->current_chat != NULL);
1323 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1325 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1326 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1327 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1331 buffer = gtk_text_view_get_buffer (
1332 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1334 if (gtk_text_buffer_get_has_selection (buffer))
1336 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1337 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1343 selection = empathy_theme_adium_get_has_selection (
1344 self->priv->current_chat->view);
1346 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1347 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1350 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1351 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1352 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1356 chat_window_cut_activate_cb (GtkAction *action,
1357 EmpathyChatWindow *self)
1359 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1361 empathy_chat_cut (self->priv->current_chat);
1365 chat_window_copy_activate_cb (GtkAction *action,
1366 EmpathyChatWindow *self)
1368 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1370 empathy_chat_copy (self->priv->current_chat);
1374 chat_window_paste_activate_cb (GtkAction *action,
1375 EmpathyChatWindow *self)
1377 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1379 empathy_chat_paste (self->priv->current_chat);
1383 chat_window_find_activate_cb (GtkAction *action,
1384 EmpathyChatWindow *self)
1386 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1388 empathy_chat_find (self->priv->current_chat);
1392 chat_window_tabs_next_activate_cb (GtkAction *action,
1393 EmpathyChatWindow *self)
1395 gint index_, numPages;
1396 gboolean wrap_around;
1398 g_object_get (gtk_settings_get_default (),
1399 "gtk-keynav-wrap-around", &wrap_around,
1402 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1403 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1405 if (index_ == (numPages - 1) && wrap_around)
1407 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1411 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1415 chat_window_tabs_previous_activate_cb (GtkAction *action,
1416 EmpathyChatWindow *self)
1418 gint index_, numPages;
1419 gboolean wrap_around;
1421 g_object_get (gtk_settings_get_default (),
1422 "gtk-keynav-wrap-around", &wrap_around,
1425 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1426 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1428 if (index_ <= 0 && wrap_around)
1430 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1435 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1439 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1440 EmpathyChatWindow *self)
1442 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1443 empathy_get_current_action_time ());
1447 chat_window_tabs_left_activate_cb (GtkAction *action,
1448 EmpathyChatWindow *self)
1451 gint index_, num_pages;
1453 chat = self->priv->current_chat;
1454 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1458 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1461 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1462 chat_window_menu_context_update (self, num_pages);
1466 chat_window_tabs_right_activate_cb (GtkAction *action,
1467 EmpathyChatWindow *self)
1470 gint index_, num_pages;
1472 chat = self->priv->current_chat;
1473 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1475 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1478 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1479 chat_window_menu_context_update (self, num_pages);
1482 static EmpathyChatWindow *
1483 empathy_chat_window_new (void)
1485 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1486 "default-width", 580,
1487 "default-height", 480,
1494 chat_window_detach_activate_cb (GtkAction *action,
1495 EmpathyChatWindow *self)
1497 EmpathyChatWindow *new_window;
1500 chat = self->priv->current_chat;
1501 new_window = empathy_chat_window_new ();
1503 empathy_chat_window_move_chat (self, new_window, chat);
1505 gtk_widget_show (GTK_WIDGET (new_window));
1509 chat_window_help_contents_activate_cb (GtkAction *action,
1510 EmpathyChatWindow *self)
1512 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1516 chat_window_help_about_activate_cb (GtkAction *action,
1517 EmpathyChatWindow *self)
1519 empathy_about_dialog_new (GTK_WINDOW (self));
1523 chat_window_delete_event_cb (GtkWidget *dialog,
1525 EmpathyChatWindow *self)
1527 EmpathyChat *chat = NULL;
1531 DEBUG ("Delete event received");
1533 for (l = self->priv->chats; l != NULL; l = l->next)
1535 if (chat_needs_close_confirmation (l->data))
1544 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1548 remove_all_chats (self);
1555 chat_window_composing_cb (EmpathyChat *chat,
1556 gboolean is_composing,
1557 EmpathyChatWindow *self)
1559 chat_window_update_chat_tab (chat);
1563 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1566 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1570 chat_window_notification_closed_cb (NotifyNotification *notify,
1571 EmpathyChatWindow *self)
1573 g_object_unref (notify);
1574 if (self->priv->notification == notify)
1575 self->priv->notification = NULL;
1579 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1580 EmpathyMessage *message,
1583 EmpathyContact *sender;
1584 const gchar *header;
1588 gboolean res, has_x_canonical_append;
1589 NotifyNotification *notification = self->priv->notification;
1591 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1594 res = g_settings_get_boolean (self->priv->gsettings_notif,
1595 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1600 sender = empathy_message_get_sender (message);
1601 header = empathy_contact_get_alias (sender);
1602 body = empathy_message_get_body (message);
1603 escaped = g_markup_escape_text (body, -1);
1605 has_x_canonical_append = empathy_notify_manager_has_capability (
1606 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1608 if (notification != NULL && !has_x_canonical_append)
1610 /* if the notification server supports x-canonical-append, it is
1611 better to not use notify_notification_update to avoid
1612 overwriting the current notification message */
1613 notify_notification_update (notification,
1614 header, escaped, NULL);
1618 /* if the notification server supports x-canonical-append,
1619 the hint will be added, so that the message from the
1620 just created notification will be automatically appended
1621 to an existing notification with the same title.
1622 In this way the previous message will not be lost: the new
1623 message will appear below it, in the same notification */
1624 const gchar *category = empathy_chat_is_room (chat)
1625 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1626 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1628 notification = empathy_notify_manager_create_notification (header,
1631 if (self->priv->notification == NULL)
1632 self->priv->notification = notification;
1634 tp_g_signal_connect_object (notification, "closed",
1635 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1637 if (has_x_canonical_append)
1639 /* We have to set a not empty string to keep libnotify happy */
1640 notify_notification_set_hint_string (notification,
1641 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1644 notify_notification_set_hint (notification,
1645 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1648 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1649 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1653 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1654 g_object_unref (pixbuf);
1657 notify_notification_show (notification, NULL);
1663 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1667 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1669 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1675 chat_window_new_message_cb (EmpathyChat *chat,
1676 EmpathyMessage *message,
1678 gboolean should_highlight,
1679 EmpathyChatWindow *self)
1682 gboolean needs_urgency;
1683 EmpathyContact *sender;
1685 has_focus = empathy_chat_window_has_focus (self);
1687 /* - if we're the sender, we play the sound if it's specified in the
1688 * preferences and we're not away.
1689 * - if we receive a message, we play the sound if it's specified in the
1690 * preferences and the window does not have focus on the chat receiving
1694 sender = empathy_message_get_sender (message);
1696 if (empathy_contact_is_user (sender))
1698 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1699 EMPATHY_SOUND_MESSAGE_OUTGOING);
1703 if (has_focus && self->priv->current_chat == chat)
1705 /* window and tab are focused so consider the message to be read */
1707 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1708 empathy_chat_messages_read (chat);
1712 /* Update the chat tab if this is the first unread message */
1713 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1715 chat_window_update_chat_tab (chat);
1718 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1719 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1720 * an unamed MUC (msn-like).
1721 * In case of a MUC, we set urgency if either:
1722 * a) the chatroom's always_urgent property is TRUE
1723 * b) the message contains our alias
1725 if (empathy_chat_is_room (chat))
1729 EmpathyChatroom *chatroom;
1731 account = empathy_chat_get_account (chat);
1732 room = empathy_chat_get_id (chat);
1734 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1737 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1738 needs_urgency = TRUE;
1740 needs_urgency = should_highlight;
1744 needs_urgency = TRUE;
1750 chat_window_set_urgency_hint (self, TRUE);
1752 /* Pending messages have already been displayed and notified in the
1753 * approver, so we don't display a notification and play a sound
1757 empathy_sound_manager_play (self->priv->sound_mgr,
1758 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1760 chat_window_show_or_update_notification (self, message, chat);
1764 /* update the number of unread messages and the window icon */
1765 chat_window_title_update (self);
1766 chat_window_icon_update (self, TRUE);
1770 chat_window_command_part (EmpathyChat *chat,
1773 EmpathyChat *chat_to_be_parted;
1774 EmpathyTpChat *tp_chat = NULL;
1776 if (strv[1] == NULL)
1778 /* No chatroom ID specified */
1779 tp_chat = empathy_chat_get_tp_chat (chat);
1782 empathy_tp_chat_leave (tp_chat, "");
1787 chat_to_be_parted = empathy_chat_window_find_chat (
1788 empathy_chat_get_account (chat), strv[1], FALSE);
1790 if (chat_to_be_parted != NULL)
1792 /* Found a chatroom matching the specified ID */
1793 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1796 empathy_tp_chat_leave (tp_chat, strv[2]);
1802 /* Going by the syntax of PART command:
1804 * /PART [<chatroom-ID>] [<reason>]
1806 * Chatroom-ID is not a must to specify a reason.
1807 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1808 * MUC then the current chatroom should be parted and srtv[1] should
1809 * be treated as part of the optional part-message. */
1810 message = g_strconcat (strv[1], " ", strv[2], NULL);
1811 tp_chat = empathy_chat_get_tp_chat (chat);
1814 empathy_tp_chat_leave (tp_chat, message);
1820 static GtkNotebook *
1821 notebook_create_window_cb (GtkNotebook *source,
1827 EmpathyChatWindow *window, *new_window;
1830 chat = EMPATHY_CHAT (page);
1831 window = chat_window_find_chat (chat);
1833 new_window = empathy_chat_window_new ();
1835 DEBUG ("Detach hook called");
1837 empathy_chat_window_move_chat (window, new_window, chat);
1839 gtk_widget_show (GTK_WIDGET (new_window));
1840 gtk_window_move (GTK_WINDOW (new_window), x, y);
1846 chat_window_page_switched_cb (GtkNotebook *notebook,
1849 EmpathyChatWindow *self)
1851 EmpathyChat *chat = EMPATHY_CHAT (child);
1853 DEBUG ("Page switched");
1855 if (self->priv->page_added)
1857 self->priv->page_added = FALSE;
1858 empathy_chat_scroll_down (chat);
1860 else if (self->priv->current_chat == chat)
1865 self->priv->current_chat = chat;
1866 empathy_chat_messages_read (chat);
1868 chat_window_update_chat_tab (chat);
1872 chat_window_page_added_cb (GtkNotebook *notebook,
1875 EmpathyChatWindow *self)
1879 /* If we just received DND to the same window, we don't want
1880 * to do anything here like removing the tab and then readding
1881 * it, so we return here and in "page-added".
1883 if (self->priv->dnd_same_window)
1885 DEBUG ("Page added (back to the same window)");
1886 self->priv->dnd_same_window = FALSE;
1890 DEBUG ("Page added");
1892 /* Get chat object */
1893 chat = EMPATHY_CHAT (child);
1895 /* Connect chat signals for this window */
1896 g_signal_connect (chat, "composing",
1897 G_CALLBACK (chat_window_composing_cb), self);
1898 g_signal_connect (chat, "new-message",
1899 G_CALLBACK (chat_window_new_message_cb), self);
1900 g_signal_connect (chat, "part-command-entered",
1901 G_CALLBACK (chat_window_command_part), NULL);
1902 g_signal_connect (chat, "notify::tp-chat",
1903 G_CALLBACK (chat_window_update_chat_tab), self);
1905 /* Set flag so we know to perform some special operations on
1906 * switch page due to the new page being added.
1908 self->priv->page_added = TRUE;
1910 /* Get list of chats up to date */
1911 self->priv->chats = g_list_append (self->priv->chats, chat);
1913 chat_window_update_chat_tab (chat);
1917 chat_window_page_removed_cb (GtkNotebook *notebook,
1920 EmpathyChatWindow *self)
1924 /* If we just received DND to the same window, we don't want
1925 * to do anything here like removing the tab and then readding
1926 * it, so we return here and in "page-added".
1928 if (self->priv->dnd_same_window)
1930 DEBUG ("Page removed (and will be readded to same window)");
1934 DEBUG ("Page removed");
1936 /* Get chat object */
1937 chat = EMPATHY_CHAT (child);
1939 /* Disconnect all signal handlers for this chat and this window */
1940 g_signal_handlers_disconnect_by_func (chat,
1941 G_CALLBACK (chat_window_composing_cb), self);
1942 g_signal_handlers_disconnect_by_func (chat,
1943 G_CALLBACK (chat_window_new_message_cb), self);
1944 g_signal_handlers_disconnect_by_func (chat,
1945 G_CALLBACK (chat_window_update_chat_tab), self);
1947 /* Keep list of chats up to date */
1948 self->priv->chats = g_list_remove (self->priv->chats, chat);
1949 empathy_chat_messages_read (chat);
1951 if (self->priv->chats == NULL)
1953 gtk_widget_destroy (GTK_WIDGET (self));
1957 chat_window_update (self, TRUE);
1962 chat_window_focus_in_event_cb (GtkWidget *widget,
1964 EmpathyChatWindow *self)
1966 empathy_chat_messages_read (self->priv->current_chat);
1968 chat_window_set_urgency_hint (self, FALSE);
1970 /* Update the title, since we now mark all unread messages as read. */
1971 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1977 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1978 EmpathyChatWindow *self)
1980 chat_window_contact_menu_update (self);
1984 chat_window_focus_out_event_cb (GtkWidget *widget,
1986 EmpathyChatWindow *self)
1988 if (self->priv->individual_mgr != NULL)
1991 /* Keep the individual manager alive so we won't fetch everything from Folks
1992 * each time we need to use it. Loading FolksAggregator can takes quite a
1993 * while (if user has a huge LDAP abook for example) and it blocks
1994 * the mainloop during most of this loading. We workaround this by loading
1995 * it when the chat window has been unfocused and so, hopefully, not impact
1996 * the reactivity of the chat window too much.
1998 * The individual manager (and so Folks) is needed to know to which
1999 * FolksIndividual a TpContact belongs, including:
2000 * - empathy_chat_get_contact_menu: to list all the personas of the contact
2001 * - empathy_display_individual_info: to invoke gnome-contacts with the
2002 * FolksIndividual.id of the contact
2003 * - drag_data_received_individual_id: to find the individual associated
2004 * with the ID we received from the DnD in order to invite him.
2006 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2008 if (!empathy_individual_manager_get_contacts_loaded (
2009 self->priv->individual_mgr))
2011 /* We want to update the contact menu when Folks is loaded so we can
2012 * list all the personas of the contact. */
2013 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2014 G_CALLBACK (contacts_loaded_cb), self, 0);
2017 g_object_notify (G_OBJECT (self), "individual-manager");
2023 chat_window_drag_drop (GtkWidget *widget,
2024 GdkDragContext *context,
2028 EmpathyChatWindow *self)
2032 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2033 if (target == GDK_NONE)
2034 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2036 if (target != GDK_NONE)
2038 gtk_drag_get_data (widget, context, target, time_);
2046 chat_window_drag_motion (GtkWidget *widget,
2047 GdkDragContext *context,
2051 EmpathyChatWindow *self)
2055 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2057 if (target != GDK_NONE)
2059 /* This is a file drag. Ensure the contact is online and set the
2060 drag type to COPY. Note that it's possible that the tab will
2061 be switched by GTK+ after a timeout from drag_motion without
2062 getting another drag_motion to disable the drop. You have
2063 to hold your mouse really still.
2065 EmpathyContact *contact;
2067 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2069 /* contact is NULL for multi-user chats. We don't do
2070 * file transfers to MUCs. We also don't send files
2071 * to offline contacts or contacts that don't support
2074 if ((contact == NULL) || !empathy_contact_is_online (contact))
2076 gdk_drag_status (context, 0, time_);
2080 if (!(empathy_contact_get_capabilities (contact)
2081 & EMPATHY_CAPABILITIES_FT))
2083 gdk_drag_status (context, 0, time_);
2087 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2091 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2092 if (target != GDK_NONE)
2094 /* This is a drag of a contact from a contact list. Set to COPY.
2095 FIXME: If this drag is to a MUC window, it invites the user.
2096 Otherwise, it opens a chat. Should we use a different drag
2097 type for invites? Should we allow ASK?
2099 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2107 drag_data_received_individual_id (EmpathyChatWindow *self,
2109 GdkDragContext *context,
2112 GtkSelectionData *selection,
2117 FolksIndividual *individual;
2118 EmpathyTpChat *chat;
2119 TpContact *tp_contact;
2121 EmpathyContact *contact;
2123 id = (const gchar *) gtk_selection_data_get_data (selection);
2125 DEBUG ("DND invididual %s", id);
2127 if (self->priv->current_chat == NULL)
2130 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2134 if (!empathy_tp_chat_can_add_contact (chat))
2136 DEBUG ("Can't invite contact to %s",
2137 tp_proxy_get_object_path (chat));
2141 if (self->priv->individual_mgr == NULL)
2142 /* Not likely as we have to focus out the chat window in order to start
2143 * the DnD but best to be safe. */
2146 individual = empathy_individual_manager_lookup_member (
2147 self->priv->individual_mgr, id);
2148 if (individual == NULL)
2150 DEBUG ("Failed to find individual %s", id);
2154 conn = tp_channel_get_connection ((TpChannel *) chat);
2155 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2156 if (tp_contact == NULL)
2158 DEBUG ("Can't find a TpContact on connection %s for %s",
2159 tp_proxy_get_object_path (conn), id);
2163 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2164 tp_channel_get_identifier ((TpChannel *) chat));
2166 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2167 empathy_tp_chat_add (chat, contact, NULL);
2168 g_object_unref (contact);
2171 gtk_drag_finish (context, TRUE, FALSE, time_);
2175 chat_window_drag_data_received (GtkWidget *widget,
2176 GdkDragContext *context,
2179 GtkSelectionData *selection,
2182 EmpathyChatWindow *self)
2184 if (info == DND_DRAG_TYPE_CONTACT_ID)
2186 EmpathyChat *chat = NULL;
2187 EmpathyChatWindow *old_window;
2188 TpAccount *account = NULL;
2189 EmpathyClientFactory *factory;
2192 const gchar *account_id;
2193 const gchar *contact_id;
2195 id = (const gchar*) gtk_selection_data_get_data (selection);
2197 factory = empathy_client_factory_dup ();
2199 DEBUG ("DND contact from roster with id:'%s'", id);
2201 strv = g_strsplit (id, ":", 2);
2202 if (g_strv_length (strv) == 2)
2204 account_id = strv[0];
2205 contact_id = strv[1];
2207 account = tp_simple_client_factory_ensure_account (
2208 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2210 g_object_unref (factory);
2211 if (account != NULL)
2212 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2215 if (account == NULL)
2218 gtk_drag_finish (context, FALSE, FALSE, time_);
2224 empathy_chat_with_contact_id (account, contact_id,
2225 empathy_get_current_action_time (), NULL, NULL);
2233 old_window = chat_window_find_chat (chat);
2236 if (old_window == self)
2238 gtk_drag_finish (context, TRUE, FALSE, time_);
2242 empathy_chat_window_move_chat (old_window, self, chat);
2246 empathy_chat_window_add_chat (self, chat);
2249 /* Added to take care of any outstanding chat events */
2250 empathy_chat_window_present_chat (chat,
2251 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2253 /* We should return TRUE to remove the data when doing
2254 * GDK_ACTION_MOVE, but we don't here otherwise it has
2255 * weird consequences, and we handle that internally
2256 * anyway with add_chat () and remove_chat ().
2258 gtk_drag_finish (context, TRUE, FALSE, time_);
2260 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2262 drag_data_received_individual_id (self, widget, context, x, y,
2263 selection, info, time_);
2265 else if (info == DND_DRAG_TYPE_URI_LIST)
2267 EmpathyContact *contact;
2270 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2272 /* contact is NULL when current_chat is a multi-user chat.
2273 * We don't do file transfers to MUCs, so just cancel the drag.
2275 if (contact == NULL)
2277 gtk_drag_finish (context, TRUE, FALSE, time_);
2281 data = (const gchar *) gtk_selection_data_get_data (selection);
2282 empathy_send_file_from_uri_list (contact, data);
2284 gtk_drag_finish (context, TRUE, FALSE, time_);
2286 else if (info == DND_DRAG_TYPE_TAB)
2289 EmpathyChatWindow *old_window = NULL;
2293 chat = (void *) gtk_selection_data_get_data (selection);
2294 old_window = chat_window_find_chat (*chat);
2298 self->priv->dnd_same_window = (old_window == self);
2300 DEBUG ("DND tab (within same window: %s)",
2301 self->priv->dnd_same_window ? "Yes" : "No");
2306 DEBUG ("DND from unknown source");
2307 gtk_drag_finish (context, FALSE, FALSE, time_);
2312 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2313 guint num_chats_in_manager,
2314 EmpathyChatWindow *self)
2316 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2317 num_chats_in_manager > 0);
2321 chat_window_finalize (GObject *object)
2323 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2325 DEBUG ("Finalized: %p", object);
2327 g_object_unref (self->priv->ui_manager);
2328 g_object_unref (self->priv->chatroom_manager);
2329 g_object_unref (self->priv->notify_mgr);
2330 g_object_unref (self->priv->gsettings_chat);
2331 g_object_unref (self->priv->gsettings_notif);
2332 g_object_unref (self->priv->gsettings_ui);
2333 g_object_unref (self->priv->sound_mgr);
2334 g_clear_object (&self->priv->individual_mgr);
2336 if (self->priv->notification != NULL)
2338 notify_notification_close (self->priv->notification, NULL);
2339 self->priv->notification = NULL;
2342 if (self->priv->contact_targets)
2343 gtk_target_list_unref (self->priv->contact_targets);
2345 if (self->priv->file_targets)
2346 gtk_target_list_unref (self->priv->file_targets);
2348 if (self->priv->chat_manager)
2350 g_signal_handler_disconnect (self->priv->chat_manager,
2351 self->priv->chat_manager_chats_changed_id);
2352 g_object_unref (self->priv->chat_manager);
2353 self->priv->chat_manager = NULL;
2356 chat_windows = g_list_remove (chat_windows, self);
2358 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2362 chat_window_get_property (GObject *object,
2367 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2369 switch (property_id)
2371 case PROP_INDIVIDUAL_MGR:
2372 g_value_set_object (value, self->priv->individual_mgr);
2374 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2380 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2382 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2385 object_class->get_property = chat_window_get_property;
2386 object_class->finalize = chat_window_finalize;
2388 spec = g_param_spec_object ("individual-manager", "individual-manager",
2389 "EmpathyIndividualManager",
2390 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2391 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2392 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2394 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2398 empathy_chat_window_init (EmpathyChatWindow *self)
2401 GtkAccelGroup *accel_group;
2406 GtkWidget *chat_vbox;
2408 EmpathySmileyManager *smiley_manager;
2410 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2411 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2413 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2414 gui = empathy_builder_get_file (filename,
2415 "chat_vbox", &chat_vbox,
2416 "ui_manager", &self->priv->ui_manager,
2417 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2418 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2419 "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2420 "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2421 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2422 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2423 "menu_edit_cut", &self->priv->menu_edit_cut,
2424 "menu_edit_copy", &self->priv->menu_edit_copy,
2425 "menu_edit_paste", &self->priv->menu_edit_paste,
2426 "menu_edit_find", &self->priv->menu_edit_find,
2427 "menu_tabs_next", &self->priv->menu_tabs_next,
2428 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2429 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2430 "menu_tabs_left", &self->priv->menu_tabs_left,
2431 "menu_tabs_right", &self->priv->menu_tabs_right,
2432 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2436 empathy_builder_connect (gui, self,
2437 "menu_conv", "activate", chat_window_conv_activate_cb,
2438 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2439 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2440 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2441 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2442 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2443 "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2444 "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2445 "menu_conv_close", "activate", chat_window_close_activate_cb,
2446 "menu_edit", "activate", chat_window_edit_activate_cb,
2447 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2448 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2449 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2450 "menu_edit_find", "activate", chat_window_find_activate_cb,
2451 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2452 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2453 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2454 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2455 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2456 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2457 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2458 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2461 empathy_set_css_provider (GTK_WIDGET (self));
2463 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2464 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2465 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2466 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2468 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2470 self->priv->notebook = gtk_notebook_new ();
2472 g_signal_connect (self->priv->notebook, "create-window",
2473 G_CALLBACK (notebook_create_window_cb), self);
2475 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2477 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2478 "EmpathyChatWindow");
2479 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2480 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2481 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2482 gtk_widget_show (self->priv->notebook);
2485 accel_group = gtk_accel_group_new ();
2486 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2488 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2490 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2493 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2497 g_object_unref (accel_group);
2499 /* Set up drag target lists */
2500 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2501 G_N_ELEMENTS (drag_types_dest_contact));
2503 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2504 G_N_ELEMENTS (drag_types_dest_file));
2506 /* Set up smiley menu */
2507 smiley_manager = empathy_smiley_manager_dup_singleton ();
2508 submenu = empathy_smiley_menu_new (smiley_manager,
2509 chat_window_insert_smiley_activate_cb, self);
2511 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2512 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2513 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2514 g_object_unref (smiley_manager);
2516 /* Set up signals we can't do with ui file since we may need to
2517 * block/unblock them at some later stage.
2520 g_signal_connect (self, "delete_event",
2521 G_CALLBACK (chat_window_delete_event_cb), self);
2522 g_signal_connect (self, "focus_in_event",
2523 G_CALLBACK (chat_window_focus_in_event_cb), self);
2524 g_signal_connect (self, "focus_out_event",
2525 G_CALLBACK (chat_window_focus_out_event_cb), self);
2526 g_signal_connect_after (self->priv->notebook, "switch_page",
2527 G_CALLBACK (chat_window_page_switched_cb), self);
2528 g_signal_connect (self->priv->notebook, "page_added",
2529 G_CALLBACK (chat_window_page_added_cb), self);
2530 g_signal_connect (self->priv->notebook, "page_removed",
2531 G_CALLBACK (chat_window_page_removed_cb), self);
2533 /* Set up drag and drop */
2534 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2535 GTK_DEST_DEFAULT_HIGHLIGHT,
2537 G_N_ELEMENTS (drag_types_dest),
2538 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2540 /* connect_after to allow GtkNotebook's built-in tab switching */
2541 g_signal_connect_after (self->priv->notebook, "drag-motion",
2542 G_CALLBACK (chat_window_drag_motion), self);
2543 g_signal_connect (self->priv->notebook, "drag-data-received",
2544 G_CALLBACK (chat_window_drag_data_received), self);
2545 g_signal_connect (self->priv->notebook, "drag-drop",
2546 G_CALLBACK (chat_window_drag_drop), self);
2548 chat_windows = g_list_prepend (chat_windows, self);
2550 /* Set up private details */
2551 self->priv->chats = NULL;
2552 self->priv->current_chat = NULL;
2553 self->priv->notification = NULL;
2555 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2557 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2558 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2559 self->priv->chat_manager, "closed-chats-changed",
2560 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2562 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2563 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2565 g_object_ref (self->priv->ui_manager);
2566 g_object_unref (gui);
2569 /* Returns the window to open a new tab in if there is a suitable window,
2570 * otherwise, returns NULL indicating that a new window should be added.
2572 static EmpathyChatWindow *
2573 empathy_chat_window_get_default (gboolean room)
2575 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2577 gboolean separate_windows = TRUE;
2579 separate_windows = g_settings_get_boolean (gsettings,
2580 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2582 g_object_unref (gsettings);
2584 if (separate_windows)
2585 /* Always create a new window */
2588 for (l = chat_windows; l; l = l->next)
2590 EmpathyChatWindow *chat_window;
2591 guint nb_rooms, nb_private;
2593 chat_window = l->data;
2595 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2597 /* Skip the window if there aren't any rooms in it */
2598 if (room && nb_rooms == 0)
2601 /* Skip the window if there aren't any 1-1 chats in it */
2602 if (!room && nb_private == 0)
2612 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2616 GtkWidget *popup_label;
2618 GValue value = { 0, };
2620 g_return_if_fail (self != NULL);
2621 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2623 /* Reference the chat object */
2624 g_object_ref (chat);
2626 /* If this window has just been created, position it */
2627 if (self->priv->chats == NULL)
2629 const gchar *name = "chat-window";
2630 gboolean separate_windows;
2632 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2633 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2635 if (empathy_chat_is_room (chat))
2636 name = "room-window";
2638 if (separate_windows)
2642 /* Save current position of the window */
2643 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2645 /* First bind to the 'generic' name. So new window for which we didn't
2646 * save a geometry yet will have the geometry of the last saved
2647 * window (bgo #601191). */
2648 empathy_geometry_bind (GTK_WINDOW (self), name);
2650 /* Restore previous position of the window so the newly created window
2651 * won't be in the same position as the latest saved window and so
2652 * completely hide it. */
2653 gtk_window_move (GTK_WINDOW (self), x, y);
2655 /* Then bind it to the name of the contact/room so we'll save the
2656 * geometry specific to this window */
2657 name = empathy_chat_get_id (chat);
2660 empathy_geometry_bind (GTK_WINDOW (self), name);
2663 child = GTK_WIDGET (chat);
2664 label = chat_window_create_label (self, chat, TRUE);
2665 popup_label = chat_window_create_label (self, chat, FALSE);
2666 gtk_widget_show (child);
2668 g_signal_connect (chat, "notify::name",
2669 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2670 g_signal_connect (chat, "notify::subject",
2671 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2672 g_signal_connect (chat, "notify::remote-contact",
2673 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2674 g_signal_connect (chat, "notify::sms-channel",
2675 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2676 g_signal_connect (chat, "notify::n-messages-sending",
2677 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2678 g_signal_connect (chat, "notify::nb-unread-messages",
2679 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2680 chat_window_chat_notify_cb (chat);
2682 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2684 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2685 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2686 g_value_init (&value, G_TYPE_BOOLEAN);
2687 g_value_set_boolean (&value, TRUE);
2688 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2689 child, "tab-expand" , &value);
2690 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2691 child, "tab-fill" , &value);
2692 g_value_unset (&value);
2694 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2698 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2702 EmpathyContact *remote_contact;
2703 EmpathyChatManager *chat_manager;
2705 g_return_if_fail (self != NULL);
2706 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2708 g_signal_handlers_disconnect_by_func (chat,
2709 chat_window_chat_notify_cb, NULL);
2711 remote_contact = g_object_get_data (G_OBJECT (chat),
2712 "chat-window-remote-contact");
2716 g_signal_handlers_disconnect_by_func (remote_contact,
2717 chat_window_update_chat_tab, chat);
2720 chat_manager = empathy_chat_manager_dup_singleton ();
2721 empathy_chat_manager_closed_chat (chat_manager, chat);
2722 g_object_unref (chat_manager);
2724 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2726 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2728 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2730 g_object_unref (chat);
2734 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2735 EmpathyChatWindow *new_window,
2740 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2741 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2742 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2744 widget = GTK_WIDGET (chat);
2746 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2747 G_OBJECT (widget)->ref_count);
2749 /* We reference here to make sure we don't loose the widget
2750 * and the EmpathyChat object during the move.
2752 g_object_ref (chat);
2753 g_object_ref (widget);
2755 empathy_chat_window_remove_chat (old_window, chat);
2756 empathy_chat_window_add_chat (new_window, chat);
2758 g_object_unref (widget);
2759 g_object_unref (chat);
2763 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2768 g_return_if_fail (self != NULL);
2769 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2771 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2774 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2779 empathy_chat_window_find_chat (TpAccount *account,
2781 gboolean sms_channel)
2785 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2787 for (l = chat_windows; l; l = l->next)
2789 EmpathyChatWindow *window = l->data;
2792 for (ll = window->priv->chats; ll; ll = ll->next)
2798 if (account == empathy_chat_get_account (chat) &&
2799 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2800 sms_channel == empathy_chat_is_sms_channel (chat))
2809 empathy_chat_window_present_chat (EmpathyChat *chat,
2812 EmpathyChatWindow *self;
2813 guint32 x_timestamp;
2815 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2817 self = chat_window_find_chat (chat);
2819 /* If the chat has no window, create one */
2822 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2825 self = empathy_chat_window_new ();
2827 /* we want to display the newly created window even if we
2828 * don't present it */
2829 gtk_widget_show (GTK_WIDGET (self));
2832 empathy_chat_window_add_chat (self, chat);
2835 /* Don't force the window to show itself when it wasn't
2836 * an action by the user
2838 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2841 if (x_timestamp != GDK_CURRENT_TIME)
2843 /* Don't present or switch tab if the action was earlier than the
2844 * last actions X time, accounting for overflow and the first ever
2847 if (self->priv->x_user_action_time != 0
2848 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2851 self->priv->x_user_action_time = x_timestamp;
2854 empathy_chat_window_switch_to_chat (self, chat);
2856 /* Don't use empathy_window_present_with_time () which would move the window
2857 * to our current desktop but move to the window's desktop instead. This is
2858 * more coherent with Shell's 'app is ready' notication which moves the view
2859 * to the app desktop rather than moving the app itself. */
2860 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2862 gtk_widget_grab_focus (chat->input_text_view);
2867 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2872 guint _nb_rooms = 0, _nb_private = 0;
2874 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2876 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2882 if (nb_rooms != NULL)
2883 *nb_rooms = _nb_rooms;
2884 if (nb_private != NULL)
2885 *nb_private = _nb_private;
2888 EmpathyIndividualManager *
2889 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2891 return self->priv->individual_mgr;