1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2003-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Richard Hult <richard@imendio.com>
23 * Martyn Russell <martyn@imendio.com>
24 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
25 * Xavier Claessens <xclaesse@gmail.com>
26 * Rômulo Fernandes Machado <romulo@castorgroup.net>
34 #include <gdk/gdkkeysyms.h>
36 #include <glib/gi18n.h>
37 #include <libnotify/notification.h>
39 #include <telepathy-glib/telepathy-glib.h>
41 #include <libempathy/empathy-client-factory.h>
42 #include <libempathy/empathy-contact.h>
43 #include <libempathy/empathy-message.h>
44 #include <libempathy/empathy-chatroom-manager.h>
45 #include <libempathy/empathy-gsettings.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-request-util.h>
48 #include <libempathy/empathy-individual-manager.h>
50 #include <libempathy-gtk/empathy-images.h>
51 #include <libempathy-gtk/empathy-contact-dialogs.h>
52 #include <libempathy-gtk/empathy-log-window.h>
53 #include <libempathy-gtk/empathy-geometry.h>
54 #include <libempathy-gtk/empathy-smiley-manager.h>
55 #include <libempathy-gtk/empathy-sound-manager.h>
56 #include <libempathy-gtk/empathy-ui-utils.h>
57 #include <libempathy-gtk/empathy-notify-manager.h>
59 #include "empathy-chat-manager.h"
60 #include "empathy-chat-window.h"
61 #include "empathy-about-dialog.h"
62 #include "empathy-invite-participant-dialog.h"
64 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
65 #include <libempathy/empathy-debug.h>
67 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
69 #define X_EARLIER_OR_EQL(t1, t2) \
70 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
71 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
74 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
76 EmpathyChat *current_chat;
79 gboolean dnd_same_window;
80 EmpathyChatroomManager *chatroom_manager;
81 EmpathyNotifyManager *notify_mgr;
84 NotifyNotification *notification;
86 GtkTargetList *contact_targets;
87 GtkTargetList *file_targets;
89 EmpathyChatManager *chat_manager;
90 gulong chat_manager_chats_changed_id;
93 GtkUIManager *ui_manager;
94 GtkAction *menu_conv_insert_smiley;
95 GtkAction *menu_conv_favorite;
96 GtkAction *menu_conv_always_urgent;
97 GtkAction *menu_conv_toggle_contacts;
99 GtkAction *menu_edit_cut;
100 GtkAction *menu_edit_copy;
101 GtkAction *menu_edit_paste;
102 GtkAction *menu_edit_find;
104 GtkAction *menu_tabs_next;
105 GtkAction *menu_tabs_prev;
106 GtkAction *menu_tabs_undo_close_tab;
107 GtkAction *menu_tabs_left;
108 GtkAction *menu_tabs_right;
109 GtkAction *menu_tabs_detach;
111 /* Last user action time we acted upon to show a tab */
112 guint32 x_user_action_time;
114 GSettings *gsettings_chat;
115 GSettings *gsettings_notif;
116 GSettings *gsettings_ui;
118 EmpathySoundManager *sound_mgr;
119 } EmpathyChatWindowPriv;
121 static GList *chat_windows = NULL;
123 static const guint tab_accel_keys[] = {
124 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
125 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
129 DND_DRAG_TYPE_CONTACT_ID,
130 DND_DRAG_TYPE_INDIVIDUAL_ID,
131 DND_DRAG_TYPE_URI_LIST,
135 static const GtkTargetEntry drag_types_dest[] = {
136 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
137 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
138 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
139 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
140 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] = {
144 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
145 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
148 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, G_TYPE_OBJECT);
175 chat_window_accel_cb (GtkAccelGroup *accelgroup,
179 EmpathyChatWindow *window)
181 EmpathyChatWindowPriv *priv;
185 priv = GET_PRIV (window);
187 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
188 if (tab_accel_keys[i] == key) {
195 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
199 static EmpathyChatWindow *
200 chat_window_find_chat (EmpathyChat *chat)
202 EmpathyChatWindowPriv *priv;
205 for (l = chat_windows; l; l = l->next) {
206 priv = GET_PRIV (l->data);
207 ll = g_list_find (priv->chats, chat);
217 remove_all_chats (EmpathyChatWindow *window)
219 EmpathyChatWindowPriv *priv;
221 priv = GET_PRIV (window);
222 g_object_ref (window);
224 while (priv->chats) {
225 empathy_chat_window_remove_chat (window, priv->chats->data);
228 g_object_unref (window);
232 confirm_close_response_cb (GtkWidget *dialog,
234 EmpathyChatWindow *window)
238 chat = g_object_get_data (G_OBJECT (dialog), "chat");
240 gtk_widget_destroy (dialog);
242 if (response != GTK_RESPONSE_ACCEPT)
246 empathy_chat_window_remove_chat (window, chat);
248 remove_all_chats (window);
253 confirm_close (EmpathyChatWindow *window,
254 gboolean close_window,
258 EmpathyChatWindowPriv *priv;
260 gchar *primary, *secondary;
262 g_return_if_fail (n_rooms > 0);
265 g_return_if_fail (chat == NULL);
267 g_return_if_fail (chat != NULL);
270 priv = GET_PRIV (window);
272 /* If there are no chats in this window, how could we possibly have got
275 g_return_if_fail (priv->chats != NULL);
277 /* Treat closing a window which only has one tab exactly like closing
280 if (close_window && priv->chats->next == NULL) {
281 close_window = FALSE;
282 chat = priv->chats->data;
286 primary = g_strdup (_("Close this window?"));
289 gchar *chat_name = empathy_chat_dup_name (chat);
290 secondary = g_strdup_printf (
291 _("Closing this window will leave %s. You will "
292 "not receive any further messages until you "
297 secondary = g_strdup_printf (
298 /* Note to translators: the number of chats will
299 * always be at least 2.
302 "Closing this window will leave a chat room. You will "
303 "not receive any further messages until you rejoin it.",
304 "Closing this window will leave %u chat rooms. You will "
305 "not receive any further messages until you rejoin them.",
310 gchar *chat_name = empathy_chat_dup_name (chat);
311 primary = g_strdup_printf (_("Leave %s?"), chat_name);
312 secondary = g_strdup (_("You will not receive any further messages from this chat "
313 "room until you rejoin it."));
317 dialog = gtk_message_dialog_new (
318 GTK_WINDOW (priv->dialog),
319 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
324 gtk_window_set_title (GTK_WINDOW (dialog), "");
325 g_object_set (dialog, "secondary-text", secondary, NULL);
330 gtk_dialog_add_button (GTK_DIALOG (dialog),
331 close_window ? _("Close window") : _("Leave room"),
332 GTK_RESPONSE_ACCEPT);
333 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
334 GTK_RESPONSE_ACCEPT);
337 g_object_set_data (G_OBJECT (dialog), "chat", chat);
340 g_signal_connect (dialog, "response",
341 G_CALLBACK (confirm_close_response_cb), window);
343 gtk_window_present (GTK_WINDOW (dialog));
346 /* Returns TRUE if we should check if the user really wants to leave. If it's
347 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
348 * the user is actually in the room as opposed to having been kicked or gone
349 * offline or something), then we should check.
352 chat_needs_close_confirmation (EmpathyChat *chat)
354 return (empathy_chat_is_room (chat)
355 && empathy_chat_get_tp_chat (chat) != NULL);
359 maybe_close_chat (EmpathyChatWindow *window,
362 g_return_if_fail (chat != NULL);
364 if (chat_needs_close_confirmation (chat)) {
365 confirm_close (window, FALSE, 1, chat);
367 empathy_chat_window_remove_chat (window, chat);
372 chat_window_close_clicked_cb (GtkAction *action,
375 EmpathyChatWindow *window;
377 window = chat_window_find_chat (chat);
378 maybe_close_chat (window, chat);
382 chat_tab_style_updated_cb (GtkWidget *hbox,
386 int char_width, h, w;
387 PangoContext *context;
388 const PangoFontDescription *font_desc;
389 PangoFontMetrics *metrics;
391 button = g_object_get_data (G_OBJECT (user_data),
392 "chat-window-tab-close-button");
393 context = gtk_widget_get_pango_context (hbox);
395 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
396 GTK_STATE_FLAG_NORMAL);
398 metrics = pango_context_get_metrics (context, font_desc,
399 pango_context_get_language (context));
400 char_width = pango_font_metrics_get_approximate_char_width (metrics);
401 pango_font_metrics_unref (metrics);
403 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
404 GTK_ICON_SIZE_MENU, &w, &h);
406 /* Request at least about 12 chars width plus at least space for the status
407 * image and the close button */
408 gtk_widget_set_size_request (hbox,
409 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
411 gtk_widget_set_size_request (button, w, h);
415 create_close_button (void)
417 GtkWidget *button, *image;
418 GtkStyleContext *context;
420 button = gtk_button_new ();
422 context = gtk_widget_get_style_context (button);
423 gtk_style_context_add_class (context, "empathy-tab-close-button");
425 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
426 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
428 /* We don't want focus/keynav for the button to avoid clutter, and
429 * Ctrl-W works anyway.
431 gtk_widget_set_can_focus (button, FALSE);
432 gtk_widget_set_can_default (button, FALSE);
434 image = gtk_image_new_from_icon_name ("window-close-symbolic",
436 gtk_widget_show (image);
438 gtk_container_add (GTK_CONTAINER (button), image);
444 chat_window_create_label (EmpathyChatWindow *window,
446 gboolean is_tab_label)
449 GtkWidget *name_label;
450 GtkWidget *status_image;
451 GtkWidget *event_box;
452 GtkWidget *event_box_hbox;
453 PangoAttrList *attr_list;
454 PangoAttribute *attr;
456 /* The spacing between the button and the label. */
457 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
459 event_box = gtk_event_box_new ();
460 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
462 name_label = gtk_label_new (NULL);
464 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
466 attr_list = pango_attr_list_new ();
467 attr = pango_attr_scale_new (1/1.2);
468 attr->start_index = 0;
469 attr->end_index = -1;
470 pango_attr_list_insert (attr_list, attr);
471 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
472 pango_attr_list_unref (attr_list);
474 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
475 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
476 g_object_set_data (G_OBJECT (chat),
477 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
480 status_image = gtk_image_new ();
482 /* Spacing between the icon and label. */
483 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
485 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
486 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
488 g_object_set_data (G_OBJECT (chat),
489 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
491 g_object_set_data (G_OBJECT (chat),
492 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
495 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
496 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
499 GtkWidget *close_button;
500 GtkWidget *sending_spinner;
502 sending_spinner = gtk_spinner_new ();
504 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
506 g_object_set_data (G_OBJECT (chat),
507 "chat-window-tab-sending-spinner",
510 close_button = create_close_button ();
511 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
513 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
515 g_signal_connect (close_button,
517 G_CALLBACK (chat_window_close_clicked_cb),
520 /* React to theme changes and also setup the size correctly. */
521 g_signal_connect (hbox,
523 G_CALLBACK (chat_tab_style_updated_cb),
527 gtk_widget_show_all (hbox);
533 _submenu_notify_visible_changed_cb (GObject *object,
537 g_signal_handlers_disconnect_by_func (object,
538 _submenu_notify_visible_changed_cb,
540 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
544 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
549 gboolean wrap_around;
550 gboolean is_connected;
553 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
554 first_page = (page_num == 0);
555 last_page = (page_num == (num_pages - 1));
556 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
558 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
560 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
562 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
564 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
565 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
566 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
567 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
571 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
572 EmpathyChatWindow *self)
574 EmpathyTpChat *tp_chat;
575 TpConnection *connection;
577 gboolean sensitive = FALSE;
579 g_return_if_fail (priv->current_chat != NULL);
581 action = gtk_ui_manager_get_action (priv->ui_manager,
582 "/chats_menubar/menu_conv/menu_conv_invite_participant");
583 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
585 if (tp_chat != NULL) {
586 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
588 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
589 (tp_connection_get_status (connection, NULL) ==
590 TP_CONNECTION_STATUS_CONNECTED);
593 gtk_action_set_sensitive (action, sensitive);
597 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
598 EmpathyChatWindow *window)
600 GtkWidget *menu, *submenu, *orig_submenu;
602 menu = gtk_ui_manager_get_widget (priv->ui_manager,
603 "/chats_menubar/menu_contact");
604 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
606 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
607 submenu = empathy_chat_get_contact_menu (priv->current_chat);
609 if (submenu != NULL) {
610 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
611 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
613 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
614 gtk_widget_show (menu);
615 gtk_widget_set_sensitive (menu, TRUE);
617 gtk_widget_set_sensitive (menu, FALSE);
620 tp_g_signal_connect_object (orig_submenu,
622 (GCallback)_submenu_notify_visible_changed_cb,
628 get_all_unread_messages (EmpathyChatWindowPriv *priv)
633 for (l = priv->chats; l != NULL; l = g_list_next (l))
634 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
640 get_window_title_name (EmpathyChatWindowPriv *priv)
642 gchar *active_name, *ret;
644 guint current_unread_msgs;
646 nb_chats = g_list_length (priv->chats);
647 g_assert (nb_chats > 0);
649 active_name = empathy_chat_dup_name (priv->current_chat);
651 current_unread_msgs = empathy_chat_get_nb_unread_messages (
656 if (current_unread_msgs == 0)
657 ret = g_strdup (active_name);
659 ret = g_strdup_printf (ngettext (
661 "%s (%d unread)", current_unread_msgs),
662 active_name, current_unread_msgs);
664 guint nb_others = nb_chats - 1;
665 guint all_unread_msgs;
667 all_unread_msgs = get_all_unread_messages (priv);
669 if (all_unread_msgs == 0) {
670 /* no unread message */
671 ret = g_strdup_printf (ngettext (
673 "%s (and %u others)", nb_others),
674 active_name, nb_others);
677 else if (all_unread_msgs == current_unread_msgs) {
678 /* unread messages are in the current tab */
679 ret = g_strdup_printf (ngettext (
681 "%s (%d unread)", current_unread_msgs),
682 active_name, current_unread_msgs);
685 else if (current_unread_msgs == 0) {
686 /* unread messages are in other tabs */
687 ret = g_strdup_printf (ngettext (
688 "%s (%d unread from others)",
689 "%s (%d unread from others)",
691 active_name, all_unread_msgs);
695 /* unread messages are in all the tabs */
696 ret = g_strdup_printf (ngettext (
697 "%s (%d unread from all)",
698 "%s (%d unread from all)",
700 active_name, all_unread_msgs);
704 g_free (active_name);
710 chat_window_title_update (EmpathyChatWindowPriv *priv)
714 name = get_window_title_name (priv);
715 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
720 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
723 EmpathyContact *remote_contact;
724 gboolean avatar_in_icon;
727 n_chats = g_list_length (priv->chats);
729 /* Update window icon */
731 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
732 EMPATHY_IMAGE_MESSAGE);
734 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
735 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
737 if (n_chats == 1 && avatar_in_icon) {
738 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
739 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
740 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
743 g_object_unref (icon);
746 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
752 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
756 GtkWidget *chat_close_button;
759 if (num_pages == 1) {
760 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
761 chat_close_button = g_object_get_data (G_OBJECT (chat),
762 "chat-window-tab-close-button");
763 gtk_widget_hide (chat_close_button);
765 for (i=0; i<num_pages; i++) {
766 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
767 chat_close_button = g_object_get_data (G_OBJECT (chat),
768 "chat-window-tab-close-button");
769 gtk_widget_show (chat_close_button);
775 chat_window_update (EmpathyChatWindow *window,
776 gboolean update_contact_menu)
778 EmpathyChatWindowPriv *priv = GET_PRIV (window);
781 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
783 /* Update Tab menu */
784 chat_window_menu_context_update (priv,
787 chat_window_conversation_menu_update (priv, window);
789 /* If this update is due to a focus-in event, we know the menu will be
790 the same as when we last left it, so no work to do. Besides, if we
791 swap out the menu on a focus-in, we may confuse any external global
793 if (update_contact_menu) {
794 chat_window_contact_menu_update (priv,
798 chat_window_title_update (priv);
800 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
802 chat_window_close_button_update (priv,
807 append_markup_printf (GString *string,
814 va_start (args, format);
816 tmp = g_markup_vprintf_escaped (format, args);
817 g_string_append (string, tmp);
824 chat_window_update_chat_tab_full (EmpathyChat *chat,
825 gboolean update_contact_menu)
827 EmpathyChatWindow *window;
828 EmpathyChatWindowPriv *priv;
829 EmpathyContact *remote_contact;
833 const gchar *subject;
834 const gchar *status = NULL;
838 const gchar *icon_name;
839 GtkWidget *tab_image;
840 GtkWidget *menu_image;
841 GtkWidget *sending_spinner;
844 window = chat_window_find_chat (chat);
848 priv = GET_PRIV (window);
850 /* Get information */
851 name = empathy_chat_dup_name (chat);
852 account = empathy_chat_get_account (chat);
853 subject = empathy_chat_get_subject (chat);
854 remote_contact = empathy_chat_get_remote_contact (chat);
856 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
857 name, tp_proxy_get_object_path (account), subject, remote_contact);
859 /* Update tab image */
860 if (empathy_chat_get_tp_chat (chat) == NULL) {
861 /* No TpChat, we are disconnected */
864 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
865 icon_name = EMPATHY_IMAGE_MESSAGE;
867 else if (remote_contact && empathy_chat_is_composing (chat)) {
868 icon_name = EMPATHY_IMAGE_TYPING;
870 else if (empathy_chat_is_sms_channel (chat)) {
871 icon_name = EMPATHY_IMAGE_SMS;
873 else if (remote_contact) {
874 icon_name = empathy_icon_name_for_contact (remote_contact);
876 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
879 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
880 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
881 if (icon_name != NULL) {
882 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
883 gtk_widget_show (tab_image);
884 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
885 gtk_widget_show (menu_image);
887 gtk_widget_hide (tab_image);
888 gtk_widget_hide (menu_image);
891 /* Update the sending spinner */
892 nb_sending = empathy_chat_get_n_messages_sending (chat);
893 sending_spinner = g_object_get_data (G_OBJECT (chat),
894 "chat-window-tab-sending-spinner");
896 g_object_set (sending_spinner,
897 "active", nb_sending > 0,
898 "visible", nb_sending > 0,
901 /* Update tab tooltip */
902 tooltip = g_string_new (NULL);
904 if (remote_contact) {
905 id = empathy_contact_get_id (remote_contact);
906 status = empathy_contact_get_presence_message (remote_contact);
911 if (empathy_chat_is_sms_channel (chat)) {
912 append_markup_printf (tooltip, "%s ", _("SMS:"));
915 append_markup_printf (tooltip,
916 "<b>%s</b><small> (%s)</small>",
918 tp_account_get_display_name (account));
920 if (nb_sending > 0) {
921 char *tmp = g_strdup_printf (
922 ngettext ("Sending %d message",
923 "Sending %d messages",
927 g_string_append (tooltip, "\n");
928 g_string_append (tooltip, tmp);
930 gtk_widget_set_tooltip_text (sending_spinner, tmp);
934 if (!EMP_STR_EMPTY (status)) {
935 append_markup_printf (tooltip, "\n<i>%s</i>", status);
939 append_markup_printf (tooltip, "\n<b>%s</b> %s",
940 _("Topic:"), subject);
943 if (remote_contact && empathy_chat_is_composing (chat)) {
944 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
947 if (remote_contact != NULL) {
948 const gchar * const *types;
950 types = empathy_contact_get_client_types (remote_contact);
951 if (types != NULL && !tp_strdiff (types[0], "phone")) {
952 /* I'm on a phone ! */
955 name = g_strdup_printf ("☎ %s", name);
960 markup = g_string_free (tooltip, FALSE);
961 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
962 gtk_widget_set_tooltip_markup (widget, markup);
963 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
964 gtk_widget_set_tooltip_markup (widget, markup);
967 /* Update tab and menu label */
968 if (empathy_chat_is_highlighted (chat)) {
969 markup = g_markup_printf_escaped (
970 "<span color=\"red\" weight=\"bold\">%s</span>",
973 markup = g_markup_escape_text (name, -1);
976 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
977 gtk_label_set_markup (GTK_LABEL (widget), markup);
978 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
979 gtk_label_set_markup (GTK_LABEL (widget), markup);
982 /* Update the window if it's the current chat */
983 if (priv->current_chat == chat) {
984 chat_window_update (window, update_contact_menu);
991 chat_window_update_chat_tab (EmpathyChat *chat)
993 chat_window_update_chat_tab_full (chat, TRUE);
997 chat_window_chat_notify_cb (EmpathyChat *chat)
999 EmpathyChatWindow *window;
1000 EmpathyContact *old_remote_contact;
1001 EmpathyContact *remote_contact = NULL;
1003 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
1004 remote_contact = empathy_chat_get_remote_contact (chat);
1006 if (old_remote_contact != remote_contact) {
1007 /* The remote-contact associated with the chat changed, we need
1008 * to keep track of any change of that contact and update the
1009 * window each time. */
1010 if (remote_contact) {
1011 g_signal_connect_swapped (remote_contact, "notify",
1012 G_CALLBACK (chat_window_update_chat_tab),
1015 if (old_remote_contact) {
1016 g_signal_handlers_disconnect_by_func (old_remote_contact,
1017 chat_window_update_chat_tab,
1021 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1022 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1025 chat_window_update_chat_tab (chat);
1027 window = chat_window_find_chat (chat);
1028 if (window != NULL) {
1029 chat_window_update (window, FALSE);
1034 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1035 EmpathySmiley *smiley,
1038 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1040 GtkTextBuffer *buffer;
1043 chat = priv->current_chat;
1045 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1046 gtk_text_buffer_get_end_iter (buffer, &iter);
1047 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1051 chat_window_conv_activate_cb (GtkAction *action,
1052 EmpathyChatWindow *window)
1054 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1057 EmpathyContact *remote_contact = NULL;
1059 /* Favorite room menu */
1060 is_room = empathy_chat_is_room (priv->current_chat);
1064 gboolean found = FALSE;
1065 EmpathyChatroom *chatroom;
1067 room = empathy_chat_get_id (priv->current_chat);
1068 account = empathy_chat_get_account (priv->current_chat);
1069 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1071 if (chatroom != NULL)
1072 found = empathy_chatroom_is_favorite (chatroom);
1074 DEBUG ("This room %s favorite", found ? "is" : "is not");
1075 gtk_toggle_action_set_active (
1076 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1078 if (chatroom != NULL)
1079 found = empathy_chatroom_is_always_urgent (chatroom);
1081 gtk_toggle_action_set_active (
1082 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1085 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1086 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1088 /* Show contacts menu */
1089 g_object_get (priv->current_chat,
1090 "remote-contact", &remote_contact,
1091 "show-contacts", &active,
1093 if (remote_contact == NULL) {
1094 gtk_toggle_action_set_active (
1095 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1098 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1099 (remote_contact == NULL));
1100 if (remote_contact != NULL) {
1101 g_object_unref (remote_contact);
1106 chat_window_clear_activate_cb (GtkAction *action,
1107 EmpathyChatWindow *window)
1109 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1111 empathy_chat_clear (priv->current_chat);
1115 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1116 EmpathyChatWindow *window)
1118 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1123 EmpathyChatroom *chatroom;
1125 active = gtk_toggle_action_get_active (toggle_action);
1126 account = empathy_chat_get_account (priv->current_chat);
1127 room = empathy_chat_get_id (priv->current_chat);
1128 name = empathy_chat_dup_name (priv->current_chat);
1130 chatroom = empathy_chatroom_manager_ensure_chatroom (
1131 priv->chatroom_manager,
1136 empathy_chatroom_set_favorite (chatroom, active);
1137 g_object_unref (chatroom);
1142 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1143 EmpathyChatWindow *window)
1145 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1150 EmpathyChatroom *chatroom;
1152 active = gtk_toggle_action_get_active (toggle_action);
1153 account = empathy_chat_get_account (priv->current_chat);
1154 room = empathy_chat_get_id (priv->current_chat);
1155 name = empathy_chat_dup_name (priv->current_chat);
1157 chatroom = empathy_chatroom_manager_ensure_chatroom (
1158 priv->chatroom_manager,
1163 empathy_chatroom_set_always_urgent (chatroom, active);
1164 g_object_unref (chatroom);
1169 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1170 EmpathyChatWindow *window)
1172 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1175 active = gtk_toggle_action_get_active (toggle_action);
1177 empathy_chat_set_show_contacts (priv->current_chat, active);
1181 chat_window_invite_participant_activate_cb (GtkAction *action,
1182 EmpathyChatWindow *window)
1184 EmpathyChatWindowPriv *priv;
1186 EmpathyTpChat *tp_chat;
1189 priv = GET_PRIV (window);
1191 g_return_if_fail (priv->current_chat != NULL);
1193 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1195 dialog = empathy_invite_participant_dialog_new (
1196 GTK_WINDOW (priv->dialog), tp_chat);
1197 gtk_widget_show (dialog);
1199 response = gtk_dialog_run (GTK_DIALOG (dialog));
1201 if (response == GTK_RESPONSE_ACCEPT) {
1202 TpContact *tp_contact;
1203 EmpathyContact *contact;
1205 tp_contact = empathy_invite_participant_dialog_get_selected (
1206 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1207 if (tp_contact == NULL) goto out;
1209 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1211 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1213 g_object_unref (contact);
1217 gtk_widget_destroy (dialog);
1221 chat_window_close_activate_cb (GtkAction *action,
1222 EmpathyChatWindow *window)
1224 EmpathyChatWindowPriv *priv;
1226 priv = GET_PRIV (window);
1228 g_return_if_fail (priv->current_chat != NULL);
1230 maybe_close_chat (window, priv->current_chat);
1234 chat_window_edit_activate_cb (GtkAction *action,
1235 EmpathyChatWindow *window)
1237 EmpathyChatWindowPriv *priv;
1238 GtkClipboard *clipboard;
1239 GtkTextBuffer *buffer;
1240 gboolean text_available;
1242 priv = GET_PRIV (window);
1244 g_return_if_fail (priv->current_chat != NULL);
1246 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1247 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1248 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1249 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1253 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1254 if (gtk_text_buffer_get_has_selection (buffer)) {
1255 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1256 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1260 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1262 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1263 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1266 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1267 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1268 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1272 chat_window_cut_activate_cb (GtkAction *action,
1273 EmpathyChatWindow *window)
1275 EmpathyChatWindowPriv *priv;
1277 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1279 priv = GET_PRIV (window);
1281 empathy_chat_cut (priv->current_chat);
1285 chat_window_copy_activate_cb (GtkAction *action,
1286 EmpathyChatWindow *window)
1288 EmpathyChatWindowPriv *priv;
1290 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1292 priv = GET_PRIV (window);
1294 empathy_chat_copy (priv->current_chat);
1298 chat_window_paste_activate_cb (GtkAction *action,
1299 EmpathyChatWindow *window)
1301 EmpathyChatWindowPriv *priv;
1303 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1305 priv = GET_PRIV (window);
1307 empathy_chat_paste (priv->current_chat);
1311 chat_window_find_activate_cb (GtkAction *action,
1312 EmpathyChatWindow *window)
1314 EmpathyChatWindowPriv *priv;
1316 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1318 priv = GET_PRIV (window);
1320 empathy_chat_find (priv->current_chat);
1324 chat_window_tabs_next_activate_cb (GtkAction *action,
1325 EmpathyChatWindow *window)
1327 EmpathyChatWindowPriv *priv;
1328 gint index_, numPages;
1329 gboolean wrap_around;
1331 priv = GET_PRIV (window);
1333 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1334 &wrap_around, NULL);
1336 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1337 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1339 if (index_ == (numPages - 1) && wrap_around) {
1340 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1344 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1348 chat_window_tabs_previous_activate_cb (GtkAction *action,
1349 EmpathyChatWindow *window)
1351 EmpathyChatWindowPriv *priv;
1352 gint index_, numPages;
1353 gboolean wrap_around;
1355 priv = GET_PRIV (window);
1357 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1358 &wrap_around, NULL);
1360 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1361 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1363 if (index_ <= 0 && wrap_around) {
1364 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1368 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1372 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1373 EmpathyChatWindow *window)
1375 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1376 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1377 empathy_get_current_action_time ());
1381 chat_window_tabs_left_activate_cb (GtkAction *action,
1382 EmpathyChatWindow *window)
1384 EmpathyChatWindowPriv *priv;
1386 gint index_, num_pages;
1388 priv = GET_PRIV (window);
1390 chat = priv->current_chat;
1391 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1396 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1400 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1401 chat_window_menu_context_update (priv, num_pages);
1405 chat_window_tabs_right_activate_cb (GtkAction *action,
1406 EmpathyChatWindow *window)
1408 EmpathyChatWindowPriv *priv;
1410 gint index_, num_pages;
1412 priv = GET_PRIV (window);
1414 chat = priv->current_chat;
1415 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1417 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1421 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1422 chat_window_menu_context_update (priv, num_pages);
1425 static EmpathyChatWindow *
1426 empathy_chat_window_new (void)
1428 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1432 chat_window_detach_activate_cb (GtkAction *action,
1433 EmpathyChatWindow *window)
1435 EmpathyChatWindowPriv *priv;
1436 EmpathyChatWindow *new_window;
1439 priv = GET_PRIV (window);
1441 chat = priv->current_chat;
1442 new_window = empathy_chat_window_new ();
1444 empathy_chat_window_move_chat (window, new_window, chat);
1446 priv = GET_PRIV (new_window);
1447 gtk_widget_show (priv->dialog);
1451 chat_window_help_contents_activate_cb (GtkAction *action,
1452 EmpathyChatWindow *window)
1454 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1456 empathy_url_show (priv->dialog, "help:empathy");
1460 chat_window_help_about_activate_cb (GtkAction *action,
1461 EmpathyChatWindow *window)
1463 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1465 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1469 chat_window_delete_event_cb (GtkWidget *dialog,
1471 EmpathyChatWindow *window)
1473 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1474 EmpathyChat *chat = NULL;
1478 DEBUG ("Delete event received");
1480 for (l = priv->chats; l != NULL; l = l->next) {
1481 if (chat_needs_close_confirmation (l->data)) {
1488 confirm_close (window, TRUE, n_rooms,
1489 (n_rooms == 1 ? chat : NULL));
1491 remove_all_chats (window);
1498 chat_window_composing_cb (EmpathyChat *chat,
1499 gboolean is_composing,
1500 EmpathyChatWindow *window)
1502 chat_window_update_chat_tab (chat);
1506 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1509 EmpathyChatWindowPriv *priv;
1511 priv = GET_PRIV (window);
1513 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1517 chat_window_notification_closed_cb (NotifyNotification *notify,
1518 EmpathyChatWindow *self)
1520 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1522 g_object_unref (notify);
1523 if (priv->notification == notify) {
1524 priv->notification = NULL;
1529 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1530 EmpathyMessage *message,
1533 EmpathyContact *sender;
1534 const gchar *header;
1538 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1539 gboolean res, has_x_canonical_append;
1540 NotifyNotification *notification = priv->notification;
1542 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1545 res = g_settings_get_boolean (priv->gsettings_notif,
1546 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1553 sender = empathy_message_get_sender (message);
1554 header = empathy_contact_get_alias (sender);
1555 body = empathy_message_get_body (message);
1556 escaped = g_markup_escape_text (body, -1);
1557 has_x_canonical_append = empathy_notify_manager_has_capability (
1558 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1560 if (notification != NULL && !has_x_canonical_append) {
1561 /* if the notification server supports x-canonical-append, it is
1562 better to not use notify_notification_update to avoid
1563 overwriting the current notification message */
1564 notify_notification_update (notification,
1565 header, escaped, NULL);
1567 /* if the notification server supports x-canonical-append,
1568 the hint will be added, so that the message from the
1569 just created notification will be automatically appended
1570 to an existing notification with the same title.
1571 In this way the previous message will not be lost: the new
1572 message will appear below it, in the same notification */
1573 notification = notify_notification_new (header, escaped, NULL);
1575 if (priv->notification == NULL) {
1576 priv->notification = notification;
1579 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1581 tp_g_signal_connect_object (notification, "closed",
1582 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1584 if (has_x_canonical_append) {
1585 /* We have to set a not empty string to keep libnotify happy */
1586 notify_notification_set_hint_string (notification,
1587 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1591 const gchar *category = empathy_chat_is_room (chat)
1592 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1593 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1594 notify_notification_set_hint (notification,
1595 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1596 g_variant_new_string (category));
1600 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1601 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1603 if (pixbuf != NULL) {
1604 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1605 g_object_unref (pixbuf);
1608 notify_notification_show (notification, NULL);
1614 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1616 EmpathyChatWindowPriv *priv;
1619 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1621 priv = GET_PRIV (window);
1623 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1629 chat_window_new_message_cb (EmpathyChat *chat,
1630 EmpathyMessage *message,
1632 gboolean should_highlight,
1633 EmpathyChatWindow *window)
1635 EmpathyChatWindowPriv *priv;
1637 gboolean needs_urgency;
1638 EmpathyContact *sender;
1640 priv = GET_PRIV (window);
1642 has_focus = empathy_chat_window_has_focus (window);
1644 /* - if we're the sender, we play the sound if it's specified in the
1645 * preferences and we're not away.
1646 * - if we receive a message, we play the sound if it's specified in the
1647 * preferences and the window does not have focus on the chat receiving
1651 sender = empathy_message_get_sender (message);
1653 if (empathy_contact_is_user (sender)) {
1654 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1655 EMPATHY_SOUND_MESSAGE_OUTGOING);
1658 if (has_focus && priv->current_chat == chat) {
1659 /* window and tab are focused so consider the message to be read */
1661 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1662 empathy_chat_messages_read (chat);
1666 /* Update the chat tab if this is the first unread message */
1667 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1668 chat_window_update_chat_tab (chat);
1671 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1672 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1673 * an unamed MUC (msn-like).
1674 * In case of a MUC, we set urgency if either:
1675 * a) the chatroom's always_urgent property is TRUE
1676 * b) the message contains our alias
1678 if (empathy_chat_is_room (chat)) {
1681 EmpathyChatroom *chatroom;
1683 account = empathy_chat_get_account (chat);
1684 room = empathy_chat_get_id (chat);
1686 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1689 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1690 needs_urgency = TRUE;
1692 needs_urgency = should_highlight;
1695 needs_urgency = TRUE;
1698 if (needs_urgency) {
1700 chat_window_set_urgency_hint (window, TRUE);
1703 /* Pending messages have already been displayed and notified in the
1704 * approver, so we don't display a notification and play a sound for those */
1706 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1707 EMPATHY_SOUND_MESSAGE_INCOMING);
1709 chat_window_show_or_update_notification (window, message, chat);
1713 /* update the number of unread messages and the window icon */
1714 chat_window_title_update (priv);
1715 chat_window_icon_update (priv, TRUE);
1719 chat_window_command_part (EmpathyChat *chat,
1722 EmpathyChat *chat_to_be_parted;
1723 EmpathyTpChat *tp_chat = NULL;
1725 if (strv[1] == NULL) {
1726 /* No chatroom ID specified */
1727 tp_chat = empathy_chat_get_tp_chat (chat);
1729 empathy_tp_chat_leave (tp_chat, "");
1732 chat_to_be_parted = empathy_chat_window_find_chat (
1733 empathy_chat_get_account (chat), strv[1], FALSE);
1735 if (chat_to_be_parted != NULL) {
1736 /* Found a chatroom matching the specified ID */
1737 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1739 empathy_tp_chat_leave (tp_chat, strv[2]);
1743 /* Going by the syntax of PART command:
1745 * /PART [<chatroom-ID>] [<reason>]
1747 * Chatroom-ID is not a must to specify a reason.
1748 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1749 * MUC then the current chatroom should be parted and srtv[1] should
1750 * be treated as part of the optional part-message. */
1751 message = g_strconcat (strv[1], " ", strv[2], NULL);
1752 tp_chat = empathy_chat_get_tp_chat (chat);
1754 empathy_tp_chat_leave (tp_chat, message);
1760 static GtkNotebook *
1761 notebook_create_window_cb (GtkNotebook *source,
1767 EmpathyChatWindowPriv *priv;
1768 EmpathyChatWindow *window, *new_window;
1771 chat = EMPATHY_CHAT (page);
1772 window = chat_window_find_chat (chat);
1774 new_window = empathy_chat_window_new ();
1775 priv = GET_PRIV (new_window);
1777 DEBUG ("Detach hook called");
1779 empathy_chat_window_move_chat (window, new_window, chat);
1781 gtk_widget_show (priv->dialog);
1782 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1788 chat_window_page_switched_cb (GtkNotebook *notebook,
1791 EmpathyChatWindow *window)
1793 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1794 EmpathyChat *chat = EMPATHY_CHAT (child);
1796 DEBUG ("Page switched");
1798 if (priv->page_added) {
1799 priv->page_added = FALSE;
1800 empathy_chat_scroll_down (chat);
1802 else if (priv->current_chat == chat) {
1806 priv->current_chat = chat;
1807 empathy_chat_messages_read (chat);
1809 chat_window_update_chat_tab (chat);
1813 chat_window_page_added_cb (GtkNotebook *notebook,
1816 EmpathyChatWindow *window)
1818 EmpathyChatWindowPriv *priv;
1821 priv = GET_PRIV (window);
1823 /* If we just received DND to the same window, we don't want
1824 * to do anything here like removing the tab and then readding
1825 * it, so we return here and in "page-added".
1827 if (priv->dnd_same_window) {
1828 DEBUG ("Page added (back to the same window)");
1829 priv->dnd_same_window = FALSE;
1833 DEBUG ("Page added");
1835 /* Get chat object */
1836 chat = EMPATHY_CHAT (child);
1838 /* Connect chat signals for this window */
1839 g_signal_connect (chat, "composing",
1840 G_CALLBACK (chat_window_composing_cb),
1842 g_signal_connect (chat, "new-message",
1843 G_CALLBACK (chat_window_new_message_cb),
1845 g_signal_connect (chat, "part-command-entered",
1846 G_CALLBACK (chat_window_command_part),
1848 g_signal_connect (chat, "notify::tp-chat",
1849 G_CALLBACK (chat_window_update_chat_tab),
1852 /* Set flag so we know to perform some special operations on
1853 * switch page due to the new page being added.
1855 priv->page_added = TRUE;
1857 /* Get list of chats up to date */
1858 priv->chats = g_list_append (priv->chats, chat);
1860 chat_window_update_chat_tab (chat);
1864 chat_window_page_removed_cb (GtkNotebook *notebook,
1867 EmpathyChatWindow *window)
1869 EmpathyChatWindowPriv *priv;
1872 priv = GET_PRIV (window);
1874 /* If we just received DND to the same window, we don't want
1875 * to do anything here like removing the tab and then readding
1876 * it, so we return here and in "page-added".
1878 if (priv->dnd_same_window) {
1879 DEBUG ("Page removed (and will be readded to same window)");
1883 DEBUG ("Page removed");
1885 /* Get chat object */
1886 chat = EMPATHY_CHAT (child);
1888 /* Disconnect all signal handlers for this chat and this window */
1889 g_signal_handlers_disconnect_by_func (chat,
1890 G_CALLBACK (chat_window_composing_cb),
1892 g_signal_handlers_disconnect_by_func (chat,
1893 G_CALLBACK (chat_window_new_message_cb),
1895 g_signal_handlers_disconnect_by_func (chat,
1896 G_CALLBACK (chat_window_update_chat_tab),
1899 /* Keep list of chats up to date */
1900 priv->chats = g_list_remove (priv->chats, chat);
1901 empathy_chat_messages_read (chat);
1903 if (priv->chats == NULL) {
1904 g_object_unref (window);
1906 chat_window_update (window, TRUE);
1911 chat_window_focus_in_event_cb (GtkWidget *widget,
1913 EmpathyChatWindow *window)
1915 EmpathyChatWindowPriv *priv;
1917 priv = GET_PRIV (window);
1919 empathy_chat_messages_read (priv->current_chat);
1921 chat_window_set_urgency_hint (window, FALSE);
1923 /* Update the title, since we now mark all unread messages as read. */
1924 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1930 chat_window_drag_drop (GtkWidget *widget,
1931 GdkDragContext *context,
1935 EmpathyChatWindow *window)
1938 EmpathyChatWindowPriv *priv;
1940 priv = GET_PRIV (window);
1942 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1943 if (target == GDK_NONE)
1944 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1946 if (target != GDK_NONE) {
1947 gtk_drag_get_data (widget, context, target, time_);
1955 chat_window_drag_motion (GtkWidget *widget,
1956 GdkDragContext *context,
1960 EmpathyChatWindow *window)
1963 EmpathyChatWindowPriv *priv;
1965 priv = GET_PRIV (window);
1967 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1968 if (target != GDK_NONE) {
1969 /* This is a file drag. Ensure the contact is online and set the
1970 drag type to COPY. Note that it's possible that the tab will
1971 be switched by GTK+ after a timeout from drag_motion without
1972 getting another drag_motion to disable the drop. You have
1973 to hold your mouse really still.
1975 EmpathyContact *contact;
1977 priv = GET_PRIV (window);
1978 contact = empathy_chat_get_remote_contact (priv->current_chat);
1979 /* contact is NULL for multi-user chats. We don't do
1980 * file transfers to MUCs. We also don't send files
1981 * to offline contacts or contacts that don't support
1984 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1985 gdk_drag_status (context, 0, time_);
1988 if (!(empathy_contact_get_capabilities (contact)
1989 & EMPATHY_CAPABILITIES_FT)) {
1990 gdk_drag_status (context, 0, time_);
1993 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1997 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1998 if (target != GDK_NONE) {
1999 /* This is a drag of a contact from a contact list. Set to COPY.
2000 FIXME: If this drag is to a MUC window, it invites the user.
2001 Otherwise, it opens a chat. Should we use a different drag
2002 type for invites? Should we allow ASK?
2004 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2012 drag_data_received_individual_id (EmpathyChatWindow *self,
2014 GdkDragContext *context,
2017 GtkSelectionData *selection,
2022 EmpathyIndividualManager *manager = NULL;
2023 FolksIndividual *individual;
2024 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2025 EmpathyTpChat *chat;
2026 TpContact *tp_contact;
2028 EmpathyContact *contact;
2030 id = (const gchar *) gtk_selection_data_get_data (selection);
2032 DEBUG ("DND invididual %s", id);
2034 if (priv->current_chat == NULL)
2037 chat = empathy_chat_get_tp_chat (priv->current_chat);
2041 if (!empathy_tp_chat_can_add_contact (chat)) {
2042 DEBUG ("Can't invite contact to %s",
2043 tp_proxy_get_object_path (chat));
2047 manager = empathy_individual_manager_dup_singleton ();
2049 individual = empathy_individual_manager_lookup_member (manager, id);
2050 if (individual == NULL) {
2051 DEBUG ("Failed to find individual %s", id);
2055 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2056 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2057 if (tp_contact == NULL) {
2058 DEBUG ("Can't find a TpContact on connection %s for %s",
2059 tp_proxy_get_object_path (conn), id);
2063 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2064 tp_channel_get_identifier ((TpChannel *) chat));
2066 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2067 empathy_tp_chat_add (chat, contact, NULL);
2068 g_object_unref (contact);
2071 gtk_drag_finish (context, TRUE, FALSE, time_);
2072 tp_clear_object (&manager);
2076 chat_window_drag_data_received (GtkWidget *widget,
2077 GdkDragContext *context,
2080 GtkSelectionData *selection,
2083 EmpathyChatWindow *window)
2085 if (info == DND_DRAG_TYPE_CONTACT_ID) {
2086 EmpathyChat *chat = NULL;
2087 EmpathyChatWindow *old_window;
2088 TpAccount *account = NULL;
2089 EmpathyClientFactory *factory;
2092 const gchar *account_id;
2093 const gchar *contact_id;
2095 id = (const gchar*) gtk_selection_data_get_data (selection);
2097 factory = empathy_client_factory_dup ();
2099 DEBUG ("DND contact from roster with id:'%s'", id);
2101 strv = g_strsplit (id, ":", 2);
2102 if (g_strv_length (strv) == 2) {
2103 account_id = strv[0];
2104 contact_id = strv[1];
2106 tp_simple_client_factory_ensure_account (
2107 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2110 g_object_unref (factory);
2111 if (account != NULL)
2112 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2115 if (account == NULL) {
2117 gtk_drag_finish (context, FALSE, FALSE, time_);
2122 empathy_chat_with_contact_id (
2123 account, contact_id,
2124 empathy_get_current_action_time (),
2132 old_window = chat_window_find_chat (chat);
2134 if (old_window == window) {
2135 gtk_drag_finish (context, TRUE, FALSE, time_);
2139 empathy_chat_window_move_chat (old_window, window, chat);
2141 empathy_chat_window_add_chat (window, chat);
2144 /* Added to take care of any outstanding chat events */
2145 empathy_chat_window_present_chat (chat,
2146 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2148 /* We should return TRUE to remove the data when doing
2149 * GDK_ACTION_MOVE, but we don't here otherwise it has
2150 * weird consequences, and we handle that internally
2151 * anyway with add_chat () and remove_chat ().
2153 gtk_drag_finish (context, TRUE, FALSE, time_);
2155 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2156 drag_data_received_individual_id (window, widget, context, x, y,
2157 selection, info, time_);
2159 else if (info == DND_DRAG_TYPE_URI_LIST) {
2160 EmpathyChatWindowPriv *priv;
2161 EmpathyContact *contact;
2164 priv = GET_PRIV (window);
2165 contact = empathy_chat_get_remote_contact (priv->current_chat);
2167 /* contact is NULL when current_chat is a multi-user chat.
2168 * We don't do file transfers to MUCs, so just cancel the drag.
2170 if (contact == NULL) {
2171 gtk_drag_finish (context, TRUE, FALSE, time_);
2175 data = (const gchar *) gtk_selection_data_get_data (selection);
2176 empathy_send_file_from_uri_list (contact, data);
2178 gtk_drag_finish (context, TRUE, FALSE, time_);
2180 else if (info == DND_DRAG_TYPE_TAB) {
2182 EmpathyChatWindow *old_window = NULL;
2186 chat = (void *) gtk_selection_data_get_data (selection);
2187 old_window = chat_window_find_chat (*chat);
2190 EmpathyChatWindowPriv *priv;
2192 priv = GET_PRIV (window);
2193 priv->dnd_same_window = (old_window == window);
2194 DEBUG ("DND tab (within same window: %s)",
2195 priv->dnd_same_window ? "Yes" : "No");
2198 DEBUG ("DND from unknown source");
2199 gtk_drag_finish (context, FALSE, FALSE, time_);
2204 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2205 guint num_chats_in_manager,
2206 EmpathyChatWindow *window)
2208 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2210 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2211 num_chats_in_manager > 0);
2215 chat_window_finalize (GObject *object)
2217 EmpathyChatWindow *window;
2218 EmpathyChatWindowPriv *priv;
2220 window = EMPATHY_CHAT_WINDOW (object);
2221 priv = GET_PRIV (window);
2223 DEBUG ("Finalized: %p", object);
2225 g_object_unref (priv->ui_manager);
2226 g_object_unref (priv->chatroom_manager);
2227 g_object_unref (priv->notify_mgr);
2228 g_object_unref (priv->gsettings_chat);
2229 g_object_unref (priv->gsettings_notif);
2230 g_object_unref (priv->gsettings_ui);
2231 g_object_unref (priv->sound_mgr);
2233 if (priv->notification != NULL) {
2234 notify_notification_close (priv->notification, NULL);
2235 priv->notification = NULL;
2238 if (priv->contact_targets) {
2239 gtk_target_list_unref (priv->contact_targets);
2241 if (priv->file_targets) {
2242 gtk_target_list_unref (priv->file_targets);
2245 if (priv->chat_manager) {
2246 g_signal_handler_disconnect (priv->chat_manager,
2247 priv->chat_manager_chats_changed_id);
2248 g_object_unref (priv->chat_manager);
2249 priv->chat_manager = NULL;
2252 chat_windows = g_list_remove (chat_windows, window);
2253 gtk_widget_destroy (priv->dialog);
2255 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2259 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2261 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2263 object_class->finalize = chat_window_finalize;
2265 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2269 empathy_chat_window_init (EmpathyChatWindow *window)
2272 GtkAccelGroup *accel_group;
2277 GtkWidget *chat_vbox;
2279 EmpathySmileyManager *smiley_manager;
2280 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2281 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2283 window->priv = priv;
2284 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2285 gui = empathy_builder_get_file (filename,
2286 "chat_window", &priv->dialog,
2287 "chat_vbox", &chat_vbox,
2288 "ui_manager", &priv->ui_manager,
2289 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2290 "menu_conv_favorite", &priv->menu_conv_favorite,
2291 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2292 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2293 "menu_edit_cut", &priv->menu_edit_cut,
2294 "menu_edit_copy", &priv->menu_edit_copy,
2295 "menu_edit_paste", &priv->menu_edit_paste,
2296 "menu_edit_find", &priv->menu_edit_find,
2297 "menu_tabs_next", &priv->menu_tabs_next,
2298 "menu_tabs_prev", &priv->menu_tabs_prev,
2299 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2300 "menu_tabs_left", &priv->menu_tabs_left,
2301 "menu_tabs_right", &priv->menu_tabs_right,
2302 "menu_tabs_detach", &priv->menu_tabs_detach,
2306 empathy_builder_connect (gui, window,
2307 "menu_conv", "activate", chat_window_conv_activate_cb,
2308 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2309 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2310 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2311 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2312 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2313 "menu_conv_close", "activate", chat_window_close_activate_cb,
2314 "menu_edit", "activate", chat_window_edit_activate_cb,
2315 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2316 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2317 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2318 "menu_edit_find", "activate", chat_window_find_activate_cb,
2319 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2320 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2321 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2322 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2323 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2324 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2325 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2326 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2329 g_object_ref (priv->ui_manager);
2330 g_object_unref (gui);
2332 empathy_set_css_provider (GTK_WIDGET (priv->dialog));
2334 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2335 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2336 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2337 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2339 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2341 priv->notebook = gtk_notebook_new ();
2343 g_signal_connect (priv->notebook, "create-window",
2344 G_CALLBACK (notebook_create_window_cb), window);
2346 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2347 "EmpathyChatWindow");
2348 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2349 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2350 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2351 gtk_widget_show (priv->notebook);
2354 accel_group = gtk_accel_group_new ();
2355 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2357 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2358 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2361 gtk_accel_group_connect (accel_group,
2368 g_object_unref (accel_group);
2370 /* Set up drag target lists */
2371 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2372 G_N_ELEMENTS (drag_types_dest_contact));
2373 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2374 G_N_ELEMENTS (drag_types_dest_file));
2376 /* Set up smiley menu */
2377 smiley_manager = empathy_smiley_manager_dup_singleton ();
2378 submenu = empathy_smiley_menu_new (smiley_manager,
2379 chat_window_insert_smiley_activate_cb,
2381 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2382 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2383 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2384 g_object_unref (smiley_manager);
2386 /* Set up signals we can't do with ui file since we may need to
2387 * block/unblock them at some later stage.
2390 g_signal_connect (priv->dialog,
2392 G_CALLBACK (chat_window_delete_event_cb),
2394 g_signal_connect (priv->dialog,
2396 G_CALLBACK (chat_window_focus_in_event_cb),
2398 g_signal_connect_after (priv->notebook,
2400 G_CALLBACK (chat_window_page_switched_cb),
2402 g_signal_connect (priv->notebook,
2404 G_CALLBACK (chat_window_page_added_cb),
2406 g_signal_connect (priv->notebook,
2408 G_CALLBACK (chat_window_page_removed_cb),
2411 /* Set up drag and drop */
2412 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2413 GTK_DEST_DEFAULT_HIGHLIGHT,
2415 G_N_ELEMENTS (drag_types_dest),
2416 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2418 /* connect_after to allow GtkNotebook's built-in tab switching */
2419 g_signal_connect_after (priv->notebook,
2421 G_CALLBACK (chat_window_drag_motion),
2423 g_signal_connect (priv->notebook,
2424 "drag-data-received",
2425 G_CALLBACK (chat_window_drag_data_received),
2427 g_signal_connect (priv->notebook,
2429 G_CALLBACK (chat_window_drag_drop),
2432 chat_windows = g_list_prepend (chat_windows, window);
2434 /* Set up private details */
2436 priv->current_chat = NULL;
2437 priv->notification = NULL;
2439 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2441 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2442 priv->chat_manager_chats_changed_id =
2443 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2444 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2447 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2448 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2452 /* Returns the window to open a new tab in if there is a suitable window,
2453 * otherwise, returns NULL indicating that a new window should be added.
2455 static EmpathyChatWindow *
2456 empathy_chat_window_get_default (gboolean room)
2458 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2460 gboolean separate_windows = TRUE;
2462 separate_windows = g_settings_get_boolean (gsettings,
2463 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2465 g_object_unref (gsettings);
2467 if (separate_windows) {
2468 /* Always create a new window */
2472 for (l = chat_windows; l; l = l->next) {
2473 EmpathyChatWindow *chat_window;
2474 guint nb_rooms, nb_private;
2476 chat_window = l->data;
2478 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2480 /* Skip the window if there aren't any rooms in it */
2481 if (room && nb_rooms == 0)
2484 /* Skip the window if there aren't any 1-1 chats in it */
2485 if (!room && nb_private == 0)
2495 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2498 EmpathyChatWindowPriv *priv;
2500 GtkWidget *popup_label;
2502 GValue value = { 0, };
2504 g_return_if_fail (window != NULL);
2505 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2507 priv = GET_PRIV (window);
2509 /* Reference the chat object */
2510 g_object_ref (chat);
2512 /* If this window has just been created, position it */
2513 if (priv->chats == NULL) {
2514 const gchar *name = "chat-window";
2515 gboolean separate_windows;
2517 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2518 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2520 if (empathy_chat_is_room (chat))
2521 name = "room-window";
2523 if (separate_windows) {
2526 /* Save current position of the window */
2527 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2529 /* First bind to the 'generic' name. So new window for which we didn't
2530 * save a geometry yet will have the geometry of the last saved
2531 * window (bgo #601191). */
2532 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2534 /* Restore previous position of the window so the newly created window
2535 * won't be in the same position as the latest saved window and so
2536 * completely hide it. */
2537 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2539 /* Then bind it to the name of the contact/room so we'll save the
2540 * geometry specific to this window */
2541 name = empathy_chat_get_id (chat);
2544 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2547 child = GTK_WIDGET (chat);
2548 label = chat_window_create_label (window, chat, TRUE);
2549 popup_label = chat_window_create_label (window, chat, FALSE);
2550 gtk_widget_show (child);
2552 g_signal_connect (chat, "notify::name",
2553 G_CALLBACK (chat_window_chat_notify_cb),
2555 g_signal_connect (chat, "notify::subject",
2556 G_CALLBACK (chat_window_chat_notify_cb),
2558 g_signal_connect (chat, "notify::remote-contact",
2559 G_CALLBACK (chat_window_chat_notify_cb),
2561 g_signal_connect (chat, "notify::sms-channel",
2562 G_CALLBACK (chat_window_chat_notify_cb),
2564 g_signal_connect (chat, "notify::n-messages-sending",
2565 G_CALLBACK (chat_window_chat_notify_cb),
2567 g_signal_connect (chat, "notify::nb-unread-messages",
2568 G_CALLBACK (chat_window_chat_notify_cb),
2570 chat_window_chat_notify_cb (chat);
2572 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2573 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2574 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2575 g_value_init (&value, G_TYPE_BOOLEAN);
2576 g_value_set_boolean (&value, TRUE);
2577 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2578 child, "tab-expand" , &value);
2579 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2580 child, "tab-fill" , &value);
2581 g_value_unset (&value);
2583 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2587 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2590 EmpathyChatWindowPriv *priv;
2592 EmpathyContact *remote_contact;
2593 EmpathyChatManager *chat_manager;
2595 g_return_if_fail (window != NULL);
2596 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2598 priv = GET_PRIV (window);
2600 g_signal_handlers_disconnect_by_func (chat,
2601 chat_window_chat_notify_cb,
2603 remote_contact = g_object_get_data (G_OBJECT (chat),
2604 "chat-window-remote-contact");
2605 if (remote_contact) {
2606 g_signal_handlers_disconnect_by_func (remote_contact,
2607 chat_window_update_chat_tab,
2611 chat_manager = empathy_chat_manager_dup_singleton ();
2612 empathy_chat_manager_closed_chat (chat_manager, chat);
2613 g_object_unref (chat_manager);
2615 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2617 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2619 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2621 g_object_unref (chat);
2625 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2626 EmpathyChatWindow *new_window,
2631 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2632 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2633 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2635 widget = GTK_WIDGET (chat);
2637 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2638 G_OBJECT (widget)->ref_count);
2640 /* We reference here to make sure we don't loose the widget
2641 * and the EmpathyChat object during the move.
2643 g_object_ref (chat);
2644 g_object_ref (widget);
2646 empathy_chat_window_remove_chat (old_window, chat);
2647 empathy_chat_window_add_chat (new_window, chat);
2649 g_object_unref (widget);
2650 g_object_unref (chat);
2654 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2657 EmpathyChatWindowPriv *priv;
2660 g_return_if_fail (window != NULL);
2661 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2663 priv = GET_PRIV (window);
2665 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2667 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2672 empathy_chat_window_find_chat (TpAccount *account,
2674 gboolean sms_channel)
2678 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2680 for (l = chat_windows; l; l = l->next) {
2681 EmpathyChatWindowPriv *priv;
2682 EmpathyChatWindow *window;
2686 priv = GET_PRIV (window);
2688 for (ll = priv->chats; ll; ll = ll->next) {
2693 if (account == empathy_chat_get_account (chat) &&
2694 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2695 sms_channel == empathy_chat_is_sms_channel (chat)) {
2705 empathy_chat_window_present_chat (EmpathyChat *chat,
2708 EmpathyChatWindow *window;
2709 EmpathyChatWindowPriv *priv;
2710 guint32 x_timestamp;
2712 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2714 window = chat_window_find_chat (chat);
2716 /* If the chat has no window, create one */
2717 if (window == NULL) {
2718 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2720 window = empathy_chat_window_new ();
2722 /* we want to display the newly created window even if we don't present
2724 priv = GET_PRIV (window);
2725 gtk_widget_show (priv->dialog);
2728 empathy_chat_window_add_chat (window, chat);
2731 /* Don't force the window to show itself when it wasn't
2732 * an action by the user
2734 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2737 priv = GET_PRIV (window);
2739 if (x_timestamp != GDK_CURRENT_TIME) {
2740 /* Don't present or switch tab if the action was earlier than the
2741 * last actions X time, accounting for overflow and the first ever
2744 if (priv->x_user_action_time != 0
2745 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2748 priv->x_user_action_time = x_timestamp;
2751 empathy_chat_window_switch_to_chat (window, chat);
2753 /* Don't use empathy_window_present_with_time () which would move the window
2754 * to our current desktop but move to the window's desktop instead. This is
2755 * more coherent with Shell's 'app is ready' notication which moves the view
2756 * to the app desktop rather than moving the app itself. */
2757 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2759 gtk_widget_grab_focus (chat->input_text_view);
2763 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2767 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2769 guint _nb_rooms = 0, _nb_private = 0;
2771 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2772 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2778 if (nb_rooms != NULL)
2779 *nb_rooms = _nb_rooms;
2780 if (nb_private != NULL)
2781 *nb_private = _nb_private;