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_always_urgent;
86 GtkAction *menu_conv_toggle_contacts;
88 GtkAction *menu_edit_cut;
89 GtkAction *menu_edit_copy;
90 GtkAction *menu_edit_paste;
91 GtkAction *menu_edit_find;
93 GtkAction *menu_tabs_next;
94 GtkAction *menu_tabs_prev;
95 GtkAction *menu_tabs_undo_close_tab;
96 GtkAction *menu_tabs_left;
97 GtkAction *menu_tabs_right;
98 GtkAction *menu_tabs_detach;
100 /* Last user action time we acted upon to show a tab */
101 guint32 x_user_action_time;
103 GSettings *gsettings_chat;
104 GSettings *gsettings_notif;
105 GSettings *gsettings_ui;
107 EmpathySoundManager *sound_mgr;
109 gboolean updating_menu;
112 static GList *chat_windows = NULL;
114 static const guint tab_accel_keys[] =
116 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
117 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
122 DND_DRAG_TYPE_CONTACT_ID,
123 DND_DRAG_TYPE_INDIVIDUAL_ID,
124 DND_DRAG_TYPE_URI_LIST,
128 static const GtkTargetEntry drag_types_dest[] =
130 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
131 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
132 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
133 /* FIXME: disabled because of bug #640513
134 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
135 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
139 static const GtkTargetEntry drag_types_dest_contact[] =
141 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
142 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
145 static const GtkTargetEntry drag_types_dest_file[] =
147 /* must be first to be prioritized, in order to receive the
148 * note's file path from Tomboy instead of an URI */
149 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
150 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
153 static void chat_window_update (EmpathyChatWindow *window,
154 gboolean update_contact_menu);
156 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
159 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
162 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
163 EmpathyChatWindow *new_window,
166 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
170 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_WINDOW)
173 chat_window_accel_cb (GtkAccelGroup *accelgroup,
177 EmpathyChatWindow *self)
182 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
184 if (tab_accel_keys[i] == key)
192 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
195 static EmpathyChatWindow *
196 chat_window_find_chat (EmpathyChat *chat)
200 for (l = chat_windows; l; l = l->next)
202 EmpathyChatWindow *window = l->data;
204 ll = g_list_find (window->priv->chats, chat);
213 remove_all_chats (EmpathyChatWindow *self)
217 while (self->priv->chats)
218 empathy_chat_window_remove_chat (self, self->priv->chats->data);
220 g_object_unref (self);
224 confirm_close_response_cb (GtkWidget *dialog,
226 EmpathyChatWindow *window)
230 chat = g_object_get_data (G_OBJECT (dialog), "chat");
232 gtk_widget_destroy (dialog);
234 if (response != GTK_RESPONSE_ACCEPT)
238 empathy_chat_window_remove_chat (window, chat);
240 remove_all_chats (window);
244 confirm_close (EmpathyChatWindow *self,
245 gboolean close_window,
250 gchar *primary, *secondary;
252 g_return_if_fail (n_rooms > 0);
255 g_return_if_fail (chat == NULL);
257 g_return_if_fail (chat != NULL);
259 /* If there are no chats in this window, how could we possibly have got
262 g_return_if_fail (self->priv->chats != NULL);
264 /* Treat closing a window which only has one tab exactly like closing
267 if (close_window && self->priv->chats->next == NULL)
269 close_window = FALSE;
270 chat = self->priv->chats->data;
275 primary = g_strdup (_("Close this window?"));
279 gchar *chat_name = empathy_chat_dup_name (chat);
280 secondary = g_strdup_printf (
281 _("Closing this window will leave %s. You will "
282 "not receive any further messages until you "
289 secondary = g_strdup_printf (
290 /* Note to translators: the number of chats will
291 * always be at least 2.
294 "Closing this window will leave a chat room. You will "
295 "not receive any further messages until you rejoin it.",
296 "Closing this window will leave %u chat rooms. You will "
297 "not receive any further messages until you rejoin them.",
304 gchar *chat_name = empathy_chat_dup_name (chat);
305 primary = g_strdup_printf (_("Leave %s?"), chat_name);
306 secondary = g_strdup (
307 _("You will not receive any further messages from this chat "
308 "room until you rejoin it."));
312 dialog = gtk_message_dialog_new (
314 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
319 gtk_window_set_title (GTK_WINDOW (dialog), "");
320 g_object_set (dialog, "secondary-text", secondary, NULL);
325 gtk_dialog_add_button (GTK_DIALOG (dialog),
326 close_window ? _("Close window") : _("Leave room"),
327 GTK_RESPONSE_ACCEPT);
328 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
329 GTK_RESPONSE_ACCEPT);
332 g_object_set_data (G_OBJECT (dialog), "chat", chat);
334 g_signal_connect (dialog, "response",
335 G_CALLBACK (confirm_close_response_cb), self);
337 gtk_window_present (GTK_WINDOW (dialog));
340 /* Returns TRUE if we should check if the user really wants to leave. If it's
341 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
342 * the user is actually in the room as opposed to having been kicked or gone
343 * offline or something), then we should check.
346 chat_needs_close_confirmation (EmpathyChat *chat)
348 return (empathy_chat_is_room (chat) &&
349 empathy_chat_get_tp_chat (chat) != NULL);
353 maybe_close_chat (EmpathyChatWindow *window,
356 g_return_if_fail (chat != NULL);
358 if (chat_needs_close_confirmation (chat))
359 confirm_close (window, FALSE, 1, chat);
361 empathy_chat_window_remove_chat (window, chat);
365 chat_window_close_clicked_cb (GtkAction *action,
368 EmpathyChatWindow *window;
370 window = chat_window_find_chat (chat);
371 maybe_close_chat (window, chat);
375 chat_tab_style_updated_cb (GtkWidget *hbox,
379 int char_width, h, w;
380 PangoContext *context;
381 const PangoFontDescription *font_desc;
382 PangoFontMetrics *metrics;
384 button = g_object_get_data (G_OBJECT (user_data),
385 "chat-window-tab-close-button");
386 context = gtk_widget_get_pango_context (hbox);
388 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
389 GTK_STATE_FLAG_NORMAL);
391 metrics = pango_context_get_metrics (context, font_desc,
392 pango_context_get_language (context));
393 char_width = pango_font_metrics_get_approximate_char_width (metrics);
394 pango_font_metrics_unref (metrics);
396 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
397 GTK_ICON_SIZE_MENU, &w, &h);
399 /* Request at least about 12 chars width plus at least space for the status
400 * image and the close button */
401 gtk_widget_set_size_request (hbox,
402 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
404 gtk_widget_set_size_request (button, w, h);
408 create_close_button (void)
410 GtkWidget *button, *image;
411 GtkStyleContext *context;
413 button = gtk_button_new ();
415 context = gtk_widget_get_style_context (button);
416 gtk_style_context_add_class (context, "empathy-tab-close-button");
418 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
419 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
421 /* We don't want focus/keynav for the button to avoid clutter, and
422 * Ctrl-W works anyway.
424 gtk_widget_set_can_focus (button, FALSE);
425 gtk_widget_set_can_default (button, FALSE);
427 image = gtk_image_new_from_icon_name ("window-close-symbolic",
429 gtk_widget_show (image);
431 gtk_container_add (GTK_CONTAINER (button), image);
437 chat_window_create_label (EmpathyChatWindow *window,
439 gboolean is_tab_label)
442 GtkWidget *name_label;
443 GtkWidget *status_image;
444 GtkWidget *event_box;
445 GtkWidget *event_box_hbox;
446 PangoAttrList *attr_list;
447 PangoAttribute *attr;
449 /* The spacing between the button and the label. */
450 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
452 event_box = gtk_event_box_new ();
453 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
455 name_label = gtk_label_new (NULL);
457 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
459 attr_list = pango_attr_list_new ();
460 attr = pango_attr_scale_new (1/1.2);
461 attr->start_index = 0;
462 attr->end_index = -1;
463 pango_attr_list_insert (attr_list, attr);
464 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
465 pango_attr_list_unref (attr_list);
467 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
468 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
469 g_object_set_data (G_OBJECT (chat),
470 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
473 status_image = gtk_image_new ();
475 /* Spacing between the icon and label. */
476 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
478 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
479 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
481 g_object_set_data (G_OBJECT (chat),
482 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
484 g_object_set_data (G_OBJECT (chat),
485 is_tab_label ? "chat-window-tab-tooltip-widget" :
486 "chat-window-menu-tooltip-widget",
489 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
490 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
494 GtkWidget *close_button;
495 GtkWidget *sending_spinner;
497 sending_spinner = gtk_spinner_new ();
499 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
501 g_object_set_data (G_OBJECT (chat),
502 "chat-window-tab-sending-spinner",
505 close_button = create_close_button ();
506 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
509 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
511 g_signal_connect (close_button,
513 G_CALLBACK (chat_window_close_clicked_cb), chat);
515 /* React to theme changes and also setup the size correctly. */
516 g_signal_connect (hbox, "style-updated",
517 G_CALLBACK (chat_tab_style_updated_cb), chat);
520 gtk_widget_show_all (hbox);
526 _submenu_notify_visible_changed_cb (GObject *object,
530 g_signal_handlers_disconnect_by_func (object,
531 _submenu_notify_visible_changed_cb, userdata);
533 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
537 chat_window_menu_context_update (EmpathyChatWindow *self,
542 gboolean wrap_around;
543 gboolean is_connected;
546 page_num = gtk_notebook_get_current_page (
547 GTK_NOTEBOOK (self->priv->notebook));
548 first_page = (page_num == 0);
549 last_page = (page_num == (num_pages - 1));
550 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
552 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
554 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
556 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
558 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
559 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
560 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
561 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
565 chat_window_conversation_menu_update (EmpathyChatWindow *self)
567 EmpathyTpChat *tp_chat;
568 TpConnection *connection;
570 gboolean sensitive = FALSE;
572 g_return_if_fail (self->priv->current_chat != NULL);
574 action = gtk_ui_manager_get_action (self->priv->ui_manager,
575 "/chats_menubar/menu_conv/menu_conv_invite_participant");
576 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
580 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
582 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
583 (tp_connection_get_status (connection, NULL) ==
584 TP_CONNECTION_STATUS_CONNECTED);
587 gtk_action_set_sensitive (action, sensitive);
591 chat_window_contact_menu_update (EmpathyChatWindow *self)
593 GtkWidget *menu, *submenu, *orig_submenu;
595 if (self->priv->updating_menu)
597 self->priv->updating_menu = TRUE;
599 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
600 "/chats_menubar/menu_contact");
601 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
603 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
605 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
609 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
610 g_object_set_data (G_OBJECT (submenu), "window", self);
612 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
613 gtk_widget_show (menu);
614 gtk_widget_set_sensitive (menu, TRUE);
618 gtk_widget_set_sensitive (menu, FALSE);
623 tp_g_signal_connect_object (orig_submenu,
625 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
628 self->priv->updating_menu = FALSE;
632 get_all_unread_messages (EmpathyChatWindow *self)
637 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
638 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
644 get_window_title_name (EmpathyChatWindow *self)
646 gchar *active_name, *ret;
648 guint current_unread_msgs;
650 nb_chats = g_list_length (self->priv->chats);
651 g_assert (nb_chats > 0);
653 active_name = empathy_chat_dup_name (self->priv->current_chat);
655 current_unread_msgs = empathy_chat_get_nb_unread_messages (
656 self->priv->current_chat);
661 if (current_unread_msgs == 0)
662 ret = g_strdup (active_name);
664 ret = g_strdup_printf (ngettext (
666 "%s (%d unread)", current_unread_msgs),
667 active_name, current_unread_msgs);
671 guint nb_others = nb_chats - 1;
672 guint all_unread_msgs;
674 all_unread_msgs = get_all_unread_messages (self);
676 if (all_unread_msgs == 0)
678 /* no unread message */
679 ret = g_strdup_printf (ngettext (
681 "%s (and %u others)", nb_others),
682 active_name, nb_others);
684 else if (all_unread_msgs == current_unread_msgs)
686 /* unread messages are in the current tab */
687 ret = g_strdup_printf (ngettext (
689 "%s (%d unread)", current_unread_msgs),
690 active_name, current_unread_msgs);
692 else if (current_unread_msgs == 0)
694 /* unread messages are in other tabs */
695 ret = g_strdup_printf (ngettext (
696 "%s (%d unread from others)",
697 "%s (%d unread from others)",
699 active_name, all_unread_msgs);
703 /* unread messages are in all the tabs */
704 ret = g_strdup_printf (ngettext (
705 "%s (%d unread from all)",
706 "%s (%d unread from all)",
708 active_name, all_unread_msgs);
712 g_free (active_name);
718 chat_window_title_update (EmpathyChatWindow *self)
722 name = get_window_title_name (self);
723 gtk_window_set_title (GTK_WINDOW (self), name);
728 chat_window_icon_update (EmpathyChatWindow *self,
729 gboolean new_messages)
732 EmpathyContact *remote_contact;
733 gboolean avatar_in_icon;
736 n_chats = g_list_length (self->priv->chats);
738 /* Update window icon */
741 gtk_window_set_icon_name (GTK_WINDOW (self),
742 EMPATHY_IMAGE_MESSAGE);
746 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
747 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
749 if (n_chats == 1 && avatar_in_icon)
751 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
752 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
754 gtk_window_set_icon (GTK_WINDOW (self), icon);
757 g_object_unref (icon);
761 gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
767 chat_window_close_button_update (EmpathyChatWindow *self,
771 GtkWidget *chat_close_button;
776 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
777 chat_close_button = g_object_get_data (G_OBJECT (chat),
778 "chat-window-tab-close-button");
779 gtk_widget_hide (chat_close_button);
783 for (i=0; i<num_pages; i++)
785 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
786 chat_close_button = g_object_get_data (G_OBJECT (chat),
787 "chat-window-tab-close-button");
788 gtk_widget_show (chat_close_button);
794 chat_window_update (EmpathyChatWindow *self,
795 gboolean update_contact_menu)
799 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
801 /* Update Tab menu */
802 chat_window_menu_context_update (self, num_pages);
804 chat_window_conversation_menu_update (self);
806 /* If this update is due to a focus-in event, we know the menu will be
807 the same as when we last left it, so no work to do. Besides, if we
808 swap out the menu on a focus-in, we may confuse any external global
810 if (update_contact_menu)
812 chat_window_contact_menu_update (self);
815 chat_window_title_update (self);
817 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
819 chat_window_close_button_update (self, num_pages);
823 append_markup_printf (GString *string,
830 va_start (args, format);
832 tmp = g_markup_vprintf_escaped (format, args);
833 g_string_append (string, tmp);
840 chat_window_update_chat_tab_full (EmpathyChat *chat,
841 gboolean update_contact_menu)
843 EmpathyChatWindow *self;
844 EmpathyContact *remote_contact;
848 const gchar *subject;
849 const gchar *status = NULL;
853 const gchar *icon_name;
854 GtkWidget *tab_image;
855 GtkWidget *menu_image;
856 GtkWidget *sending_spinner;
859 self = chat_window_find_chat (chat);
863 /* Get information */
864 name = empathy_chat_dup_name (chat);
865 account = empathy_chat_get_account (chat);
866 subject = empathy_chat_get_subject (chat);
867 remote_contact = empathy_chat_get_remote_contact (chat);
869 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
871 name, tp_proxy_get_object_path (account), subject, remote_contact);
873 /* Update tab image */
874 if (empathy_chat_get_tp_chat (chat) == NULL)
876 /* No TpChat, we are disconnected */
879 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
881 icon_name = EMPATHY_IMAGE_MESSAGE;
883 else if (remote_contact && empathy_chat_is_composing (chat))
885 icon_name = EMPATHY_IMAGE_TYPING;
887 else if (empathy_chat_is_sms_channel (chat))
889 icon_name = EMPATHY_IMAGE_SMS;
891 else if (remote_contact)
893 icon_name = empathy_icon_name_for_contact (remote_contact);
897 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
900 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
901 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
903 if (icon_name != NULL)
905 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
907 gtk_widget_show (tab_image);
908 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
910 gtk_widget_show (menu_image);
914 gtk_widget_hide (tab_image);
915 gtk_widget_hide (menu_image);
918 /* Update the sending spinner */
919 nb_sending = empathy_chat_get_n_messages_sending (chat);
920 sending_spinner = g_object_get_data (G_OBJECT (chat),
921 "chat-window-tab-sending-spinner");
923 g_object_set (sending_spinner,
924 "active", nb_sending > 0,
925 "visible", nb_sending > 0,
928 /* Update tab tooltip */
929 tooltip = g_string_new (NULL);
933 id = empathy_contact_get_id (remote_contact);
934 status = empathy_contact_get_presence_message (remote_contact);
941 if (empathy_chat_is_sms_channel (chat))
942 append_markup_printf (tooltip, "%s ", _("SMS:"));
944 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
945 id, tp_account_get_display_name (account));
949 char *tmp = g_strdup_printf (
950 ngettext ("Sending %d message",
951 "Sending %d messages",
955 g_string_append (tooltip, "\n");
956 g_string_append (tooltip, tmp);
958 gtk_widget_set_tooltip_text (sending_spinner, tmp);
962 if (!EMP_STR_EMPTY (status))
963 append_markup_printf (tooltip, "\n<i>%s</i>", status);
965 if (!EMP_STR_EMPTY (subject))
966 append_markup_printf (tooltip, "\n<b>%s</b> %s",
967 _("Topic:"), subject);
969 if (remote_contact && empathy_chat_is_composing (chat))
970 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
972 if (remote_contact != NULL)
974 const gchar * const *types;
976 types = empathy_contact_get_client_types (remote_contact);
977 if (empathy_client_types_contains_mobile_device ((GStrv) types))
979 /* I'm on a mobile device ! */
982 name = g_strdup_printf ("☎ %s", name);
987 markup = g_string_free (tooltip, FALSE);
988 widget = g_object_get_data (G_OBJECT (chat),
989 "chat-window-tab-tooltip-widget");
990 gtk_widget_set_tooltip_markup (widget, markup);
992 widget = g_object_get_data (G_OBJECT (chat),
993 "chat-window-menu-tooltip-widget");
994 gtk_widget_set_tooltip_markup (widget, markup);
997 /* Update tab and menu label */
998 if (empathy_chat_is_highlighted (chat))
1000 markup = g_markup_printf_escaped (
1001 "<span color=\"red\" weight=\"bold\">%s</span>",
1006 markup = g_markup_escape_text (name, -1);
1009 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1010 gtk_label_set_markup (GTK_LABEL (widget), markup);
1011 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1012 gtk_label_set_markup (GTK_LABEL (widget), markup);
1015 /* Update the window if it's the current chat */
1016 if (self->priv->current_chat == chat)
1017 chat_window_update (self, update_contact_menu);
1023 chat_window_update_chat_tab (EmpathyChat *chat)
1025 chat_window_update_chat_tab_full (chat, TRUE);
1029 chat_window_chat_notify_cb (EmpathyChat *chat)
1031 EmpathyChatWindow *window;
1032 EmpathyContact *old_remote_contact;
1033 EmpathyContact *remote_contact = NULL;
1035 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1036 "chat-window-remote-contact");
1037 remote_contact = empathy_chat_get_remote_contact (chat);
1039 if (old_remote_contact != remote_contact)
1041 /* The remote-contact associated with the chat changed, we need
1042 * to keep track of any change of that contact and update the
1043 * window each time. */
1045 g_signal_connect_swapped (remote_contact, "notify",
1046 G_CALLBACK (chat_window_update_chat_tab), chat);
1048 if (old_remote_contact)
1049 g_signal_handlers_disconnect_by_func (old_remote_contact,
1050 chat_window_update_chat_tab, chat);
1052 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1053 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1056 chat_window_update_chat_tab (chat);
1058 window = chat_window_find_chat (chat);
1060 chat_window_update (window, FALSE);
1064 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1065 EmpathySmiley *smiley,
1068 EmpathyChatWindow *self = user_data;
1070 GtkTextBuffer *buffer;
1073 chat = self->priv->current_chat;
1075 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1076 gtk_text_buffer_get_end_iter (buffer, &iter);
1077 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1081 chat_window_conv_activate_cb (GtkAction *action,
1082 EmpathyChatWindow *self)
1086 EmpathyContact *remote_contact = NULL;
1088 /* Favorite room menu */
1089 is_room = empathy_chat_is_room (self->priv->current_chat);
1094 gboolean found = FALSE;
1095 EmpathyChatroom *chatroom;
1097 room = empathy_chat_get_id (self->priv->current_chat);
1098 account = empathy_chat_get_account (self->priv->current_chat);
1099 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1102 if (chatroom != NULL)
1103 found = empathy_chatroom_is_favorite (chatroom);
1105 DEBUG ("This room %s favorite", found ? "is" : "is not");
1106 gtk_toggle_action_set_active (
1107 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1109 if (chatroom != NULL)
1110 found = empathy_chatroom_is_always_urgent (chatroom);
1112 gtk_toggle_action_set_active (
1113 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1116 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1117 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1119 /* Show contacts menu */
1120 g_object_get (self->priv->current_chat,
1121 "remote-contact", &remote_contact,
1122 "show-contacts", &active,
1125 if (remote_contact == NULL)
1127 gtk_toggle_action_set_active (
1128 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1131 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1132 (remote_contact == NULL));
1134 if (remote_contact != NULL)
1135 g_object_unref (remote_contact);
1139 chat_window_clear_activate_cb (GtkAction *action,
1140 EmpathyChatWindow *self)
1142 empathy_chat_clear (self->priv->current_chat);
1146 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1147 EmpathyChatWindow *self)
1153 EmpathyChatroom *chatroom;
1155 active = gtk_toggle_action_get_active (toggle_action);
1156 account = empathy_chat_get_account (self->priv->current_chat);
1157 room = empathy_chat_get_id (self->priv->current_chat);
1158 name = empathy_chat_dup_name (self->priv->current_chat);
1160 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1161 account, room, name);
1163 empathy_chatroom_set_favorite (chatroom, active);
1164 g_object_unref (chatroom);
1169 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1170 EmpathyChatWindow *self)
1176 EmpathyChatroom *chatroom;
1178 active = gtk_toggle_action_get_active (toggle_action);
1179 account = empathy_chat_get_account (self->priv->current_chat);
1180 room = empathy_chat_get_id (self->priv->current_chat);
1181 name = empathy_chat_dup_name (self->priv->current_chat);
1183 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1184 account, room, name);
1186 empathy_chatroom_set_always_urgent (chatroom, active);
1187 g_object_unref (chatroom);
1192 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1193 EmpathyChatWindow *self)
1197 active = gtk_toggle_action_get_active (toggle_action);
1199 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1203 chat_window_invite_participant_activate_cb (GtkAction *action,
1204 EmpathyChatWindow *self)
1207 EmpathyTpChat *tp_chat;
1210 g_return_if_fail (self->priv->current_chat != NULL);
1212 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1214 dialog = empathy_invite_participant_dialog_new (
1215 GTK_WINDOW (self), tp_chat);
1217 gtk_widget_show (dialog);
1219 response = gtk_dialog_run (GTK_DIALOG (dialog));
1221 if (response == GTK_RESPONSE_ACCEPT)
1223 TpContact *tp_contact;
1224 EmpathyContact *contact;
1226 tp_contact = empathy_invite_participant_dialog_get_selected (
1227 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1228 if (tp_contact == NULL)
1231 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1233 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1235 g_object_unref (contact);
1239 gtk_widget_destroy (dialog);
1243 chat_window_close_activate_cb (GtkAction *action,
1244 EmpathyChatWindow *self)
1246 g_return_if_fail (self->priv->current_chat != NULL);
1248 maybe_close_chat (self, self->priv->current_chat);
1252 chat_window_edit_activate_cb (GtkAction *action,
1253 EmpathyChatWindow *self)
1255 GtkClipboard *clipboard;
1256 GtkTextBuffer *buffer;
1257 gboolean text_available;
1259 g_return_if_fail (self->priv->current_chat != NULL);
1261 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1263 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1264 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1265 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1269 buffer = gtk_text_view_get_buffer (
1270 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1272 if (gtk_text_buffer_get_has_selection (buffer))
1274 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1275 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1281 selection = empathy_theme_adium_get_has_selection (
1282 self->priv->current_chat->view);
1284 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1285 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1288 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1289 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1290 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1294 chat_window_cut_activate_cb (GtkAction *action,
1295 EmpathyChatWindow *self)
1297 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1299 empathy_chat_cut (self->priv->current_chat);
1303 chat_window_copy_activate_cb (GtkAction *action,
1304 EmpathyChatWindow *self)
1306 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1308 empathy_chat_copy (self->priv->current_chat);
1312 chat_window_paste_activate_cb (GtkAction *action,
1313 EmpathyChatWindow *self)
1315 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1317 empathy_chat_paste (self->priv->current_chat);
1321 chat_window_find_activate_cb (GtkAction *action,
1322 EmpathyChatWindow *self)
1324 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1326 empathy_chat_find (self->priv->current_chat);
1330 chat_window_tabs_next_activate_cb (GtkAction *action,
1331 EmpathyChatWindow *self)
1333 gint index_, numPages;
1334 gboolean wrap_around;
1336 g_object_get (gtk_settings_get_default (),
1337 "gtk-keynav-wrap-around", &wrap_around,
1340 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1341 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1343 if (index_ == (numPages - 1) && wrap_around)
1345 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1349 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1353 chat_window_tabs_previous_activate_cb (GtkAction *action,
1354 EmpathyChatWindow *self)
1356 gint index_, numPages;
1357 gboolean wrap_around;
1359 g_object_get (gtk_settings_get_default (),
1360 "gtk-keynav-wrap-around", &wrap_around,
1363 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1364 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1366 if (index_ <= 0 && wrap_around)
1368 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1373 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1377 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1378 EmpathyChatWindow *self)
1380 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1381 empathy_get_current_action_time ());
1385 chat_window_tabs_left_activate_cb (GtkAction *action,
1386 EmpathyChatWindow *self)
1389 gint index_, num_pages;
1391 chat = self->priv->current_chat;
1392 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1396 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1399 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1400 chat_window_menu_context_update (self, num_pages);
1404 chat_window_tabs_right_activate_cb (GtkAction *action,
1405 EmpathyChatWindow *self)
1408 gint index_, num_pages;
1410 chat = self->priv->current_chat;
1411 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1413 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1416 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1417 chat_window_menu_context_update (self, num_pages);
1420 static EmpathyChatWindow *
1421 empathy_chat_window_new (void)
1423 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1424 "default-width", 580,
1425 "default-height", 480,
1432 chat_window_detach_activate_cb (GtkAction *action,
1433 EmpathyChatWindow *self)
1435 EmpathyChatWindow *new_window;
1438 chat = self->priv->current_chat;
1439 new_window = empathy_chat_window_new ();
1441 empathy_chat_window_move_chat (self, new_window, chat);
1443 gtk_widget_show (GTK_WIDGET (new_window));
1447 chat_window_help_contents_activate_cb (GtkAction *action,
1448 EmpathyChatWindow *self)
1450 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1454 chat_window_help_about_activate_cb (GtkAction *action,
1455 EmpathyChatWindow *self)
1457 empathy_about_dialog_new (GTK_WINDOW (self));
1461 chat_window_delete_event_cb (GtkWidget *dialog,
1463 EmpathyChatWindow *self)
1465 EmpathyChat *chat = NULL;
1469 DEBUG ("Delete event received");
1471 for (l = self->priv->chats; l != NULL; l = l->next)
1473 if (chat_needs_close_confirmation (l->data))
1482 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1486 remove_all_chats (self);
1493 chat_window_composing_cb (EmpathyChat *chat,
1494 gboolean is_composing,
1495 EmpathyChatWindow *self)
1497 chat_window_update_chat_tab (chat);
1501 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1504 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1508 chat_window_notification_closed_cb (NotifyNotification *notify,
1509 EmpathyChatWindow *self)
1511 g_object_unref (notify);
1512 if (self->priv->notification == notify)
1513 self->priv->notification = NULL;
1517 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1518 EmpathyMessage *message,
1521 EmpathyContact *sender;
1522 const gchar *header;
1526 gboolean res, has_x_canonical_append;
1527 NotifyNotification *notification = self->priv->notification;
1529 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1532 res = g_settings_get_boolean (self->priv->gsettings_notif,
1533 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1538 sender = empathy_message_get_sender (message);
1539 header = empathy_contact_get_alias (sender);
1540 body = empathy_message_get_body (message);
1541 escaped = g_markup_escape_text (body, -1);
1543 has_x_canonical_append = empathy_notify_manager_has_capability (
1544 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1546 if (notification != NULL && !has_x_canonical_append)
1548 /* if the notification server supports x-canonical-append, it is
1549 better to not use notify_notification_update to avoid
1550 overwriting the current notification message */
1551 notify_notification_update (notification,
1552 header, escaped, NULL);
1556 /* if the notification server supports x-canonical-append,
1557 the hint will be added, so that the message from the
1558 just created notification will be automatically appended
1559 to an existing notification with the same title.
1560 In this way the previous message will not be lost: the new
1561 message will appear below it, in the same notification */
1562 const gchar *category = empathy_chat_is_room (chat)
1563 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1564 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1566 notification = empathy_notify_manager_create_notification (header,
1569 if (self->priv->notification == NULL)
1570 self->priv->notification = notification;
1572 tp_g_signal_connect_object (notification, "closed",
1573 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1575 if (has_x_canonical_append)
1577 /* We have to set a not empty string to keep libnotify happy */
1578 notify_notification_set_hint_string (notification,
1579 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1582 notify_notification_set_hint (notification,
1583 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1586 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1587 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1591 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1592 g_object_unref (pixbuf);
1595 notify_notification_show (notification, NULL);
1601 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1605 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1607 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1613 chat_window_new_message_cb (EmpathyChat *chat,
1614 EmpathyMessage *message,
1616 gboolean should_highlight,
1617 EmpathyChatWindow *self)
1620 gboolean needs_urgency;
1621 EmpathyContact *sender;
1623 has_focus = empathy_chat_window_has_focus (self);
1625 /* - if we're the sender, we play the sound if it's specified in the
1626 * preferences and we're not away.
1627 * - if we receive a message, we play the sound if it's specified in the
1628 * preferences and the window does not have focus on the chat receiving
1632 sender = empathy_message_get_sender (message);
1634 if (empathy_contact_is_user (sender))
1636 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1637 EMPATHY_SOUND_MESSAGE_OUTGOING);
1641 if (has_focus && self->priv->current_chat == chat)
1643 /* window and tab are focused so consider the message to be read */
1645 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1646 empathy_chat_messages_read (chat);
1650 /* Update the chat tab if this is the first unread message */
1651 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1653 chat_window_update_chat_tab (chat);
1656 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1657 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1658 * an unamed MUC (msn-like).
1659 * In case of a MUC, we set urgency if either:
1660 * a) the chatroom's always_urgent property is TRUE
1661 * b) the message contains our alias
1663 if (empathy_chat_is_room (chat))
1667 EmpathyChatroom *chatroom;
1669 account = empathy_chat_get_account (chat);
1670 room = empathy_chat_get_id (chat);
1672 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1675 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1676 needs_urgency = TRUE;
1678 needs_urgency = should_highlight;
1682 needs_urgency = TRUE;
1688 chat_window_set_urgency_hint (self, TRUE);
1690 /* Pending messages have already been displayed and notified in the
1691 * approver, so we don't display a notification and play a sound
1695 empathy_sound_manager_play (self->priv->sound_mgr,
1696 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1698 chat_window_show_or_update_notification (self, message, chat);
1702 /* update the number of unread messages and the window icon */
1703 chat_window_title_update (self);
1704 chat_window_icon_update (self, TRUE);
1708 chat_window_command_part (EmpathyChat *chat,
1711 EmpathyChat *chat_to_be_parted;
1712 EmpathyTpChat *tp_chat = NULL;
1714 if (strv[1] == NULL)
1716 /* No chatroom ID specified */
1717 tp_chat = empathy_chat_get_tp_chat (chat);
1720 empathy_tp_chat_leave (tp_chat, "");
1725 chat_to_be_parted = empathy_chat_window_find_chat (
1726 empathy_chat_get_account (chat), strv[1], FALSE);
1728 if (chat_to_be_parted != NULL)
1730 /* Found a chatroom matching the specified ID */
1731 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1734 empathy_tp_chat_leave (tp_chat, strv[2]);
1740 /* Going by the syntax of PART command:
1742 * /PART [<chatroom-ID>] [<reason>]
1744 * Chatroom-ID is not a must to specify a reason.
1745 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1746 * MUC then the current chatroom should be parted and srtv[1] should
1747 * be treated as part of the optional part-message. */
1748 message = g_strconcat (strv[1], " ", strv[2], NULL);
1749 tp_chat = empathy_chat_get_tp_chat (chat);
1752 empathy_tp_chat_leave (tp_chat, message);
1758 static GtkNotebook *
1759 notebook_create_window_cb (GtkNotebook *source,
1765 EmpathyChatWindow *window, *new_window;
1768 chat = EMPATHY_CHAT (page);
1769 window = chat_window_find_chat (chat);
1771 new_window = empathy_chat_window_new ();
1773 DEBUG ("Detach hook called");
1775 empathy_chat_window_move_chat (window, new_window, chat);
1777 gtk_widget_show (GTK_WIDGET (new_window));
1778 gtk_window_move (GTK_WINDOW (new_window), x, y);
1784 chat_window_page_switched_cb (GtkNotebook *notebook,
1787 EmpathyChatWindow *self)
1789 EmpathyChat *chat = EMPATHY_CHAT (child);
1791 DEBUG ("Page switched");
1793 if (self->priv->page_added)
1795 self->priv->page_added = FALSE;
1796 empathy_chat_scroll_down (chat);
1798 else if (self->priv->current_chat == chat)
1803 self->priv->current_chat = chat;
1804 empathy_chat_messages_read (chat);
1806 chat_window_update_chat_tab (chat);
1810 chat_window_page_added_cb (GtkNotebook *notebook,
1813 EmpathyChatWindow *self)
1817 /* If we just received DND to the same window, we don't want
1818 * to do anything here like removing the tab and then readding
1819 * it, so we return here and in "page-added".
1821 if (self->priv->dnd_same_window)
1823 DEBUG ("Page added (back to the same window)");
1824 self->priv->dnd_same_window = FALSE;
1828 DEBUG ("Page added");
1830 /* Get chat object */
1831 chat = EMPATHY_CHAT (child);
1833 /* Connect chat signals for this window */
1834 g_signal_connect (chat, "composing",
1835 G_CALLBACK (chat_window_composing_cb), self);
1836 g_signal_connect (chat, "new-message",
1837 G_CALLBACK (chat_window_new_message_cb), self);
1838 g_signal_connect (chat, "part-command-entered",
1839 G_CALLBACK (chat_window_command_part), NULL);
1840 g_signal_connect (chat, "notify::tp-chat",
1841 G_CALLBACK (chat_window_update_chat_tab), self);
1843 /* Set flag so we know to perform some special operations on
1844 * switch page due to the new page being added.
1846 self->priv->page_added = TRUE;
1848 /* Get list of chats up to date */
1849 self->priv->chats = g_list_append (self->priv->chats, chat);
1851 chat_window_update_chat_tab (chat);
1855 chat_window_page_removed_cb (GtkNotebook *notebook,
1858 EmpathyChatWindow *self)
1862 /* If we just received DND to the same window, we don't want
1863 * to do anything here like removing the tab and then readding
1864 * it, so we return here and in "page-added".
1866 if (self->priv->dnd_same_window)
1868 DEBUG ("Page removed (and will be readded to same window)");
1872 DEBUG ("Page removed");
1874 /* Get chat object */
1875 chat = EMPATHY_CHAT (child);
1877 /* Disconnect all signal handlers for this chat and this window */
1878 g_signal_handlers_disconnect_by_func (chat,
1879 G_CALLBACK (chat_window_composing_cb), self);
1880 g_signal_handlers_disconnect_by_func (chat,
1881 G_CALLBACK (chat_window_new_message_cb), self);
1882 g_signal_handlers_disconnect_by_func (chat,
1883 G_CALLBACK (chat_window_update_chat_tab), self);
1885 /* Keep list of chats up to date */
1886 self->priv->chats = g_list_remove (self->priv->chats, chat);
1887 empathy_chat_messages_read (chat);
1889 if (self->priv->chats == NULL)
1891 gtk_widget_destroy (GTK_WIDGET (self));
1895 chat_window_update (self, TRUE);
1900 chat_window_focus_in_event_cb (GtkWidget *widget,
1902 EmpathyChatWindow *self)
1904 empathy_chat_messages_read (self->priv->current_chat);
1906 chat_window_set_urgency_hint (self, FALSE);
1908 /* Update the title, since we now mark all unread messages as read. */
1909 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1915 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1916 EmpathyChatWindow *self)
1918 chat_window_contact_menu_update (self);
1922 chat_window_focus_out_event_cb (GtkWidget *widget,
1924 EmpathyChatWindow *self)
1926 if (self->priv->individual_mgr != NULL)
1929 /* Keep the individual manager alive so we won't fetch everything from Folks
1930 * each time we need to use it. Loading FolksAggregator can takes quite a
1931 * while (if user has a huge LDAP abook for example) and it blocks
1932 * the mainloop during most of this loading. We workaround this by loading
1933 * it when the chat window has been unfocused and so, hopefully, not impact
1934 * the reactivity of the chat window too much.
1936 * The individual manager (and so Folks) is needed to know to which
1937 * FolksIndividual a TpContact belongs, including:
1938 * - empathy_chat_get_contact_menu: to list all the personas of the contact
1939 * - empathy_display_individual_info: to invoke gnome-contacts with the
1940 * FolksIndividual.id of the contact
1941 * - drag_data_received_individual_id: to find the individual associated
1942 * with the ID we received from the DnD in order to invite him.
1944 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
1946 if (!empathy_individual_manager_get_contacts_loaded (
1947 self->priv->individual_mgr))
1949 /* We want to update the contact menu when Folks is loaded so we can
1950 * list all the personas of the contact. */
1951 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
1952 G_CALLBACK (contacts_loaded_cb), self, 0);
1955 g_object_notify (G_OBJECT (self), "individual-manager");
1961 chat_window_drag_drop (GtkWidget *widget,
1962 GdkDragContext *context,
1966 EmpathyChatWindow *self)
1970 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1971 if (target == GDK_NONE)
1972 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1974 if (target != GDK_NONE)
1976 gtk_drag_get_data (widget, context, target, time_);
1984 chat_window_drag_motion (GtkWidget *widget,
1985 GdkDragContext *context,
1989 EmpathyChatWindow *self)
1993 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1995 if (target != GDK_NONE)
1997 /* This is a file drag. Ensure the contact is online and set the
1998 drag type to COPY. Note that it's possible that the tab will
1999 be switched by GTK+ after a timeout from drag_motion without
2000 getting another drag_motion to disable the drop. You have
2001 to hold your mouse really still.
2003 EmpathyContact *contact;
2005 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2007 /* contact is NULL for multi-user chats. We don't do
2008 * file transfers to MUCs. We also don't send files
2009 * to offline contacts or contacts that don't support
2012 if ((contact == NULL) || !empathy_contact_is_online (contact))
2014 gdk_drag_status (context, 0, time_);
2018 if (!(empathy_contact_get_capabilities (contact)
2019 & EMPATHY_CAPABILITIES_FT))
2021 gdk_drag_status (context, 0, time_);
2025 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2029 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2030 if (target != GDK_NONE)
2032 /* This is a drag of a contact from a contact list. Set to COPY.
2033 FIXME: If this drag is to a MUC window, it invites the user.
2034 Otherwise, it opens a chat. Should we use a different drag
2035 type for invites? Should we allow ASK?
2037 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2045 drag_data_received_individual_id (EmpathyChatWindow *self,
2047 GdkDragContext *context,
2050 GtkSelectionData *selection,
2055 FolksIndividual *individual;
2056 EmpathyTpChat *chat;
2057 TpContact *tp_contact;
2059 EmpathyContact *contact;
2061 id = (const gchar *) gtk_selection_data_get_data (selection);
2063 DEBUG ("DND invididual %s", id);
2065 if (self->priv->current_chat == NULL)
2068 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2072 if (!empathy_tp_chat_can_add_contact (chat))
2074 DEBUG ("Can't invite contact to %s",
2075 tp_proxy_get_object_path (chat));
2079 if (self->priv->individual_mgr == NULL)
2080 /* Not likely as we have to focus out the chat window in order to start
2081 * the DnD but best to be safe. */
2084 individual = empathy_individual_manager_lookup_member (
2085 self->priv->individual_mgr, id);
2086 if (individual == NULL)
2088 DEBUG ("Failed to find individual %s", id);
2092 conn = tp_channel_get_connection ((TpChannel *) chat);
2093 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2094 if (tp_contact == NULL)
2096 DEBUG ("Can't find a TpContact on connection %s for %s",
2097 tp_proxy_get_object_path (conn), id);
2101 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2102 tp_channel_get_identifier ((TpChannel *) chat));
2104 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2105 empathy_tp_chat_add (chat, contact, NULL);
2106 g_object_unref (contact);
2109 gtk_drag_finish (context, TRUE, FALSE, time_);
2113 chat_window_drag_data_received (GtkWidget *widget,
2114 GdkDragContext *context,
2117 GtkSelectionData *selection,
2120 EmpathyChatWindow *self)
2122 if (info == DND_DRAG_TYPE_CONTACT_ID)
2124 EmpathyChat *chat = NULL;
2125 EmpathyChatWindow *old_window;
2126 TpAccount *account = NULL;
2127 EmpathyClientFactory *factory;
2130 const gchar *account_id;
2131 const gchar *contact_id;
2133 id = (const gchar*) gtk_selection_data_get_data (selection);
2135 factory = empathy_client_factory_dup ();
2137 DEBUG ("DND contact from roster with id:'%s'", id);
2139 strv = g_strsplit (id, ":", 2);
2140 if (g_strv_length (strv) == 2)
2142 account_id = strv[0];
2143 contact_id = strv[1];
2145 account = tp_simple_client_factory_ensure_account (
2146 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2148 g_object_unref (factory);
2149 if (account != NULL)
2150 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2153 if (account == NULL)
2156 gtk_drag_finish (context, FALSE, FALSE, time_);
2162 empathy_chat_with_contact_id (account, contact_id,
2163 empathy_get_current_action_time (), NULL, NULL);
2171 old_window = chat_window_find_chat (chat);
2174 if (old_window == self)
2176 gtk_drag_finish (context, TRUE, FALSE, time_);
2180 empathy_chat_window_move_chat (old_window, self, chat);
2184 empathy_chat_window_add_chat (self, chat);
2187 /* Added to take care of any outstanding chat events */
2188 empathy_chat_window_present_chat (chat,
2189 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2191 /* We should return TRUE to remove the data when doing
2192 * GDK_ACTION_MOVE, but we don't here otherwise it has
2193 * weird consequences, and we handle that internally
2194 * anyway with add_chat () and remove_chat ().
2196 gtk_drag_finish (context, TRUE, FALSE, time_);
2198 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2200 drag_data_received_individual_id (self, widget, context, x, y,
2201 selection, info, time_);
2203 else if (info == DND_DRAG_TYPE_URI_LIST)
2205 EmpathyContact *contact;
2208 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2210 /* contact is NULL when current_chat is a multi-user chat.
2211 * We don't do file transfers to MUCs, so just cancel the drag.
2213 if (contact == NULL)
2215 gtk_drag_finish (context, TRUE, FALSE, time_);
2219 data = (const gchar *) gtk_selection_data_get_data (selection);
2220 empathy_send_file_from_uri_list (contact, data);
2222 gtk_drag_finish (context, TRUE, FALSE, time_);
2224 else if (info == DND_DRAG_TYPE_TAB)
2227 EmpathyChatWindow *old_window = NULL;
2231 chat = (void *) gtk_selection_data_get_data (selection);
2232 old_window = chat_window_find_chat (*chat);
2236 self->priv->dnd_same_window = (old_window == self);
2238 DEBUG ("DND tab (within same window: %s)",
2239 self->priv->dnd_same_window ? "Yes" : "No");
2244 DEBUG ("DND from unknown source");
2245 gtk_drag_finish (context, FALSE, FALSE, time_);
2250 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2251 guint num_chats_in_manager,
2252 EmpathyChatWindow *self)
2254 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2255 num_chats_in_manager > 0);
2259 chat_window_finalize (GObject *object)
2261 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2263 DEBUG ("Finalized: %p", object);
2265 g_object_unref (self->priv->ui_manager);
2266 g_object_unref (self->priv->chatroom_manager);
2267 g_object_unref (self->priv->notify_mgr);
2268 g_object_unref (self->priv->gsettings_chat);
2269 g_object_unref (self->priv->gsettings_notif);
2270 g_object_unref (self->priv->gsettings_ui);
2271 g_object_unref (self->priv->sound_mgr);
2272 g_clear_object (&self->priv->individual_mgr);
2274 if (self->priv->notification != NULL)
2276 notify_notification_close (self->priv->notification, NULL);
2277 self->priv->notification = NULL;
2280 if (self->priv->contact_targets)
2281 gtk_target_list_unref (self->priv->contact_targets);
2283 if (self->priv->file_targets)
2284 gtk_target_list_unref (self->priv->file_targets);
2286 if (self->priv->chat_manager)
2288 g_signal_handler_disconnect (self->priv->chat_manager,
2289 self->priv->chat_manager_chats_changed_id);
2290 g_object_unref (self->priv->chat_manager);
2291 self->priv->chat_manager = NULL;
2294 chat_windows = g_list_remove (chat_windows, self);
2296 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2300 chat_window_get_property (GObject *object,
2305 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2307 switch (property_id)
2309 case PROP_INDIVIDUAL_MGR:
2310 g_value_set_object (value, self->priv->individual_mgr);
2312 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2318 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2320 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2323 object_class->get_property = chat_window_get_property;
2324 object_class->finalize = chat_window_finalize;
2326 spec = g_param_spec_object ("individual-manager", "individual-manager",
2327 "EmpathyIndividualManager",
2328 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2329 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2330 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2332 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2336 empathy_chat_window_init (EmpathyChatWindow *self)
2339 GtkAccelGroup *accel_group;
2344 GtkWidget *chat_vbox;
2346 EmpathySmileyManager *smiley_manager;
2348 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2349 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2351 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2352 gui = empathy_builder_get_file (filename,
2353 "chat_vbox", &chat_vbox,
2354 "ui_manager", &self->priv->ui_manager,
2355 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2356 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2357 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2358 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2359 "menu_edit_cut", &self->priv->menu_edit_cut,
2360 "menu_edit_copy", &self->priv->menu_edit_copy,
2361 "menu_edit_paste", &self->priv->menu_edit_paste,
2362 "menu_edit_find", &self->priv->menu_edit_find,
2363 "menu_tabs_next", &self->priv->menu_tabs_next,
2364 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2365 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2366 "menu_tabs_left", &self->priv->menu_tabs_left,
2367 "menu_tabs_right", &self->priv->menu_tabs_right,
2368 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2372 empathy_builder_connect (gui, self,
2373 "menu_conv", "activate", chat_window_conv_activate_cb,
2374 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2375 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2376 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2377 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2378 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2379 "menu_conv_close", "activate", chat_window_close_activate_cb,
2380 "menu_edit", "activate", chat_window_edit_activate_cb,
2381 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2382 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2383 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2384 "menu_edit_find", "activate", chat_window_find_activate_cb,
2385 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2386 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2387 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2388 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2389 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2390 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2391 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2392 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2395 empathy_set_css_provider (GTK_WIDGET (self));
2397 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2398 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2399 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2400 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2402 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2404 self->priv->notebook = gtk_notebook_new ();
2406 g_signal_connect (self->priv->notebook, "create-window",
2407 G_CALLBACK (notebook_create_window_cb), self);
2409 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2411 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2412 "EmpathyChatWindow");
2413 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2414 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2415 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2416 gtk_widget_show (self->priv->notebook);
2419 accel_group = gtk_accel_group_new ();
2420 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2422 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2424 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2427 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2431 g_object_unref (accel_group);
2433 /* Set up drag target lists */
2434 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2435 G_N_ELEMENTS (drag_types_dest_contact));
2437 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2438 G_N_ELEMENTS (drag_types_dest_file));
2440 /* Set up smiley menu */
2441 smiley_manager = empathy_smiley_manager_dup_singleton ();
2442 submenu = empathy_smiley_menu_new (smiley_manager,
2443 chat_window_insert_smiley_activate_cb, self);
2445 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2446 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2447 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2448 g_object_unref (smiley_manager);
2450 /* Set up signals we can't do with ui file since we may need to
2451 * block/unblock them at some later stage.
2454 g_signal_connect (self, "delete_event",
2455 G_CALLBACK (chat_window_delete_event_cb), self);
2456 g_signal_connect (self, "focus_in_event",
2457 G_CALLBACK (chat_window_focus_in_event_cb), self);
2458 g_signal_connect (self, "focus_out_event",
2459 G_CALLBACK (chat_window_focus_out_event_cb), self);
2460 g_signal_connect_after (self->priv->notebook, "switch_page",
2461 G_CALLBACK (chat_window_page_switched_cb), self);
2462 g_signal_connect (self->priv->notebook, "page_added",
2463 G_CALLBACK (chat_window_page_added_cb), self);
2464 g_signal_connect (self->priv->notebook, "page_removed",
2465 G_CALLBACK (chat_window_page_removed_cb), self);
2467 /* Set up drag and drop */
2468 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2469 GTK_DEST_DEFAULT_HIGHLIGHT,
2471 G_N_ELEMENTS (drag_types_dest),
2472 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2474 /* connect_after to allow GtkNotebook's built-in tab switching */
2475 g_signal_connect_after (self->priv->notebook, "drag-motion",
2476 G_CALLBACK (chat_window_drag_motion), self);
2477 g_signal_connect (self->priv->notebook, "drag-data-received",
2478 G_CALLBACK (chat_window_drag_data_received), self);
2479 g_signal_connect (self->priv->notebook, "drag-drop",
2480 G_CALLBACK (chat_window_drag_drop), self);
2482 chat_windows = g_list_prepend (chat_windows, self);
2484 /* Set up private details */
2485 self->priv->chats = NULL;
2486 self->priv->current_chat = NULL;
2487 self->priv->notification = NULL;
2489 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2491 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2492 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2493 self->priv->chat_manager, "closed-chats-changed",
2494 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2496 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2497 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2499 g_object_ref (self->priv->ui_manager);
2500 g_object_unref (gui);
2503 /* Returns the window to open a new tab in if there is a suitable window,
2504 * otherwise, returns NULL indicating that a new window should be added.
2506 static EmpathyChatWindow *
2507 empathy_chat_window_get_default (gboolean room)
2509 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2511 gboolean separate_windows = TRUE;
2513 separate_windows = g_settings_get_boolean (gsettings,
2514 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2516 g_object_unref (gsettings);
2518 if (separate_windows)
2519 /* Always create a new window */
2522 for (l = chat_windows; l; l = l->next)
2524 EmpathyChatWindow *chat_window;
2525 guint nb_rooms, nb_private;
2527 chat_window = l->data;
2529 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2531 /* Skip the window if there aren't any rooms in it */
2532 if (room && nb_rooms == 0)
2535 /* Skip the window if there aren't any 1-1 chats in it */
2536 if (!room && nb_private == 0)
2546 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2550 GtkWidget *popup_label;
2552 GValue value = { 0, };
2554 g_return_if_fail (self != NULL);
2555 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2557 /* Reference the chat object */
2558 g_object_ref (chat);
2560 /* If this window has just been created, position it */
2561 if (self->priv->chats == NULL)
2563 const gchar *name = "chat-window";
2564 gboolean separate_windows;
2566 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2567 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2569 if (empathy_chat_is_room (chat))
2570 name = "room-window";
2572 if (separate_windows)
2576 /* Save current position of the window */
2577 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2579 /* First bind to the 'generic' name. So new window for which we didn't
2580 * save a geometry yet will have the geometry of the last saved
2581 * window (bgo #601191). */
2582 empathy_geometry_bind (GTK_WINDOW (self), name);
2584 /* Restore previous position of the window so the newly created window
2585 * won't be in the same position as the latest saved window and so
2586 * completely hide it. */
2587 gtk_window_move (GTK_WINDOW (self), x, y);
2589 /* Then bind it to the name of the contact/room so we'll save the
2590 * geometry specific to this window */
2591 name = empathy_chat_get_id (chat);
2594 empathy_geometry_bind (GTK_WINDOW (self), name);
2597 child = GTK_WIDGET (chat);
2598 label = chat_window_create_label (self, chat, TRUE);
2599 popup_label = chat_window_create_label (self, chat, FALSE);
2600 gtk_widget_show (child);
2602 g_signal_connect (chat, "notify::name",
2603 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2604 g_signal_connect (chat, "notify::subject",
2605 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2606 g_signal_connect (chat, "notify::remote-contact",
2607 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2608 g_signal_connect (chat, "notify::sms-channel",
2609 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2610 g_signal_connect (chat, "notify::n-messages-sending",
2611 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2612 g_signal_connect (chat, "notify::nb-unread-messages",
2613 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2614 chat_window_chat_notify_cb (chat);
2616 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2618 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2619 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2620 g_value_init (&value, G_TYPE_BOOLEAN);
2621 g_value_set_boolean (&value, TRUE);
2622 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2623 child, "tab-expand" , &value);
2624 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2625 child, "tab-fill" , &value);
2626 g_value_unset (&value);
2628 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2632 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2636 EmpathyContact *remote_contact;
2637 EmpathyChatManager *chat_manager;
2639 g_return_if_fail (self != NULL);
2640 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2642 g_signal_handlers_disconnect_by_func (chat,
2643 chat_window_chat_notify_cb, NULL);
2645 remote_contact = g_object_get_data (G_OBJECT (chat),
2646 "chat-window-remote-contact");
2650 g_signal_handlers_disconnect_by_func (remote_contact,
2651 chat_window_update_chat_tab, chat);
2654 chat_manager = empathy_chat_manager_dup_singleton ();
2655 empathy_chat_manager_closed_chat (chat_manager, chat);
2656 g_object_unref (chat_manager);
2658 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2660 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2662 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2664 g_object_unref (chat);
2668 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2669 EmpathyChatWindow *new_window,
2674 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2675 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2676 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2678 widget = GTK_WIDGET (chat);
2680 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2681 G_OBJECT (widget)->ref_count);
2683 /* We reference here to make sure we don't loose the widget
2684 * and the EmpathyChat object during the move.
2686 g_object_ref (chat);
2687 g_object_ref (widget);
2689 empathy_chat_window_remove_chat (old_window, chat);
2690 empathy_chat_window_add_chat (new_window, chat);
2692 g_object_unref (widget);
2693 g_object_unref (chat);
2697 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2702 g_return_if_fail (self != NULL);
2703 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2705 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2708 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2713 empathy_chat_window_find_chat (TpAccount *account,
2715 gboolean sms_channel)
2719 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2721 for (l = chat_windows; l; l = l->next)
2723 EmpathyChatWindow *window = l->data;
2726 for (ll = window->priv->chats; ll; ll = ll->next)
2732 if (account == empathy_chat_get_account (chat) &&
2733 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2734 sms_channel == empathy_chat_is_sms_channel (chat))
2743 empathy_chat_window_present_chat (EmpathyChat *chat,
2746 EmpathyChatWindow *self;
2747 guint32 x_timestamp;
2749 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2751 self = chat_window_find_chat (chat);
2753 /* If the chat has no window, create one */
2756 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2759 self = empathy_chat_window_new ();
2761 /* we want to display the newly created window even if we
2762 * don't present it */
2763 gtk_widget_show (GTK_WIDGET (self));
2766 empathy_chat_window_add_chat (self, chat);
2769 /* Don't force the window to show itself when it wasn't
2770 * an action by the user
2772 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2775 if (x_timestamp != GDK_CURRENT_TIME)
2777 /* Don't present or switch tab if the action was earlier than the
2778 * last actions X time, accounting for overflow and the first ever
2781 if (self->priv->x_user_action_time != 0
2782 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2785 self->priv->x_user_action_time = x_timestamp;
2788 empathy_chat_window_switch_to_chat (self, chat);
2790 /* Don't use empathy_window_present_with_time () which would move the window
2791 * to our current desktop but move to the window's desktop instead. This is
2792 * more coherent with Shell's 'app is ready' notication which moves the view
2793 * to the app desktop rather than moving the app itself. */
2794 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2796 gtk_widget_grab_focus (chat->input_text_view);
2801 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2806 guint _nb_rooms = 0, _nb_private = 0;
2808 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2810 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2816 if (nb_rooms != NULL)
2817 *nb_rooms = _nb_rooms;
2818 if (nb_private != NULL)
2819 *nb_private = _nb_private;
2822 EmpathyIndividualManager *
2823 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2825 return self->priv->individual_mgr;