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>
32 #include <tp-account-widgets/tpaw-builder.h>
33 #include <tp-account-widgets/tpaw-utils.h>
35 #include "empathy-about-dialog.h"
36 #include "empathy-chat-manager.h"
37 #include "empathy-chatroom-manager.h"
38 #include "empathy-client-factory.h"
39 #include "empathy-geometry.h"
40 #include "empathy-gsettings.h"
41 #include "empathy-images.h"
42 #include "empathy-invite-participant-dialog.h"
43 #include "empathy-notify-manager.h"
44 #include "empathy-request-util.h"
45 #include "empathy-smiley-manager.h"
46 #include "empathy-sound-manager.h"
47 #include "empathy-ui-utils.h"
48 #include "empathy-utils.h"
50 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
51 #include "empathy-debug.h"
53 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
55 #define X_EARLIER_OR_EQL(t1, t2) \
56 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
57 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
62 PROP_INDIVIDUAL_MGR = 1
65 struct _EmpathyChatWindowPriv
67 EmpathyChat *current_chat;
70 gboolean dnd_same_window;
71 EmpathyChatroomManager *chatroom_manager;
72 EmpathyNotifyManager *notify_mgr;
73 EmpathyIndividualManager *individual_mgr;
75 NotifyNotification *notification;
77 GtkTargetList *contact_targets;
78 GtkTargetList *file_targets;
80 EmpathyChatManager *chat_manager;
81 gulong chat_manager_chats_changed_id;
84 GtkUIManager *ui_manager;
85 GtkAction *menu_conv_insert_smiley;
86 GtkAction *menu_conv_favorite;
87 GtkAction *menu_conv_join_chat;
88 GtkAction *menu_conv_leave_chat;
89 GtkAction *menu_conv_always_urgent;
90 GtkAction *menu_conv_toggle_contacts;
92 GtkAction *menu_edit_cut;
93 GtkAction *menu_edit_copy;
94 GtkAction *menu_edit_paste;
95 GtkAction *menu_edit_find;
97 GtkAction *menu_tabs_next;
98 GtkAction *menu_tabs_prev;
99 GtkAction *menu_tabs_undo_close_tab;
100 GtkAction *menu_tabs_left;
101 GtkAction *menu_tabs_right;
102 GtkAction *menu_tabs_detach;
104 /* Last user action time we acted upon to show a tab */
105 guint32 x_user_action_time;
107 GSettings *gsettings_chat;
108 GSettings *gsettings_notif;
109 GSettings *gsettings_ui;
111 EmpathySoundManager *sound_mgr;
113 gboolean updating_menu;
116 static GList *chat_windows = NULL;
118 static const guint tab_accel_keys[] =
120 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
121 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
126 DND_DRAG_TYPE_CONTACT_ID,
127 DND_DRAG_TYPE_INDIVIDUAL_ID,
128 DND_DRAG_TYPE_URI_LIST,
132 static const GtkTargetEntry drag_types_dest[] =
134 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
135 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
136 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
137 /* FIXME: disabled because of bug #640513
138 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
139 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] =
145 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
146 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
149 static const GtkTargetEntry drag_types_dest_file[] =
151 /* must be first to be prioritized, in order to receive the
152 * note's file path from Tomboy instead of an URI */
153 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
154 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
157 static void chat_window_update (EmpathyChatWindow *window,
158 gboolean update_contact_menu);
160 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
163 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
166 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
167 EmpathyChatWindow *new_window,
170 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
174 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_WINDOW)
177 chat_window_accel_cb (GtkAccelGroup *accelgroup,
181 EmpathyChatWindow *self)
186 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
188 if (tab_accel_keys[i] == key)
196 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
199 static EmpathyChatWindow *
200 chat_window_find_chat (EmpathyChat *chat)
204 for (l = chat_windows; l; l = l->next)
206 EmpathyChatWindow *window = l->data;
208 ll = g_list_find (window->priv->chats, chat);
217 remove_all_chats (EmpathyChatWindow *self)
221 while (self->priv->chats)
222 empathy_chat_window_remove_chat (self, self->priv->chats->data);
224 g_object_unref (self);
228 confirm_close_response_cb (GtkWidget *dialog,
230 EmpathyChatWindow *window)
234 chat = g_object_get_data (G_OBJECT (dialog), "chat");
236 gtk_widget_destroy (dialog);
238 if (response != GTK_RESPONSE_ACCEPT)
242 empathy_chat_window_remove_chat (window, chat);
244 remove_all_chats (window);
248 confirm_close (EmpathyChatWindow *self,
249 gboolean close_window,
254 gchar *primary, *secondary;
256 g_return_if_fail (n_rooms > 0);
259 g_return_if_fail (chat == NULL);
261 g_return_if_fail (chat != NULL);
263 /* If there are no chats in this window, how could we possibly have got
266 g_return_if_fail (self->priv->chats != NULL);
268 /* Treat closing a window which only has one tab exactly like closing
271 if (close_window && self->priv->chats->next == NULL)
273 close_window = FALSE;
274 chat = self->priv->chats->data;
279 primary = g_strdup (_("Close this window?"));
283 gchar *chat_name = empathy_chat_dup_name (chat);
284 secondary = g_strdup_printf (
285 _("Closing this window will leave %s. You will "
286 "not receive any further messages until you "
293 secondary = g_strdup_printf (
294 /* Note to translators: the number of chats will
295 * always be at least 2.
298 "Closing this window will leave a chat room. You will "
299 "not receive any further messages until you rejoin it.",
300 "Closing this window will leave %u chat rooms. You will "
301 "not receive any further messages until you rejoin them.",
308 gchar *chat_name = empathy_chat_dup_name (chat);
309 primary = g_strdup_printf (_("Leave %s?"), chat_name);
310 secondary = g_strdup (
311 _("You will not receive any further messages from this chat "
312 "room until you rejoin it."));
316 dialog = gtk_message_dialog_new (
318 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
323 gtk_window_set_title (GTK_WINDOW (dialog), "");
324 g_object_set (dialog, "secondary-text", secondary, NULL);
329 gtk_dialog_add_button (GTK_DIALOG (dialog),
330 close_window ? _("Close window") : _("Leave room"),
331 GTK_RESPONSE_ACCEPT);
332 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
333 GTK_RESPONSE_ACCEPT);
336 g_object_set_data (G_OBJECT (dialog), "chat", chat);
338 g_signal_connect (dialog, "response",
339 G_CALLBACK (confirm_close_response_cb), self);
341 gtk_window_present (GTK_WINDOW (dialog));
344 /* Returns TRUE if we should check if the user really wants to leave. If it's
345 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
346 * the user is actually in the room as opposed to having been kicked or gone
347 * offline or something), then we should check.
350 chat_needs_close_confirmation (EmpathyChat *chat)
352 return (empathy_chat_is_room (chat) &&
353 empathy_chat_get_tp_chat (chat) != NULL);
357 maybe_close_chat (EmpathyChatWindow *window,
360 g_return_if_fail (chat != NULL);
362 if (chat_needs_close_confirmation (chat))
363 confirm_close (window, FALSE, 1, chat);
365 empathy_chat_window_remove_chat (window, chat);
369 chat_window_close_clicked_cb (GtkAction *action,
372 EmpathyChatWindow *window;
374 window = chat_window_find_chat (chat);
375 maybe_close_chat (window, chat);
379 chat_tab_style_updated_cb (GtkWidget *hbox,
383 int char_width, h, w;
384 PangoContext *context;
385 PangoFontDescription *font_desc;
386 PangoFontMetrics *metrics;
388 button = g_object_get_data (G_OBJECT (user_data),
389 "chat-window-tab-close-button");
390 context = gtk_widget_get_pango_context (hbox);
392 gtk_style_context_get (gtk_widget_get_style_context (hbox),
393 GTK_STATE_FLAG_NORMAL,
397 metrics = pango_context_get_metrics (context, font_desc,
398 pango_context_get_language (context));
399 char_width = pango_font_metrics_get_approximate_char_width (metrics);
400 pango_font_metrics_unref (metrics);
402 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
403 GTK_ICON_SIZE_MENU, &w, &h);
405 /* Request at least about 12 chars width plus at least space for the status
406 * image and the close button */
407 gtk_widget_set_size_request (hbox,
408 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
410 gtk_widget_set_size_request (button, w, h);
411 pango_font_description_free (font_desc);
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" :
493 "chat-window-menu-tooltip-widget",
496 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
497 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
501 GtkWidget *close_button;
502 GtkWidget *sending_spinner;
504 sending_spinner = gtk_spinner_new ();
506 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
508 g_object_set_data (G_OBJECT (chat),
509 "chat-window-tab-sending-spinner",
512 close_button = create_close_button ();
513 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
516 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
518 g_signal_connect (close_button,
520 G_CALLBACK (chat_window_close_clicked_cb), chat);
522 /* React to theme changes and also setup the size correctly. */
523 g_signal_connect (hbox, "style-updated",
524 G_CALLBACK (chat_tab_style_updated_cb), chat);
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, userdata);
540 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
544 chat_window_menu_context_update (EmpathyChatWindow *self,
549 gboolean wrap_around;
550 gboolean is_connected;
553 page_num = gtk_notebook_get_current_page (
554 GTK_NOTEBOOK (self->priv->notebook));
555 first_page = (page_num == 0);
556 last_page = (page_num == (num_pages - 1));
557 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
559 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
561 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
563 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
565 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
566 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
567 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
568 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
572 chat_window_conversation_menu_update (EmpathyChatWindow *self)
574 EmpathyTpChat *tp_chat;
575 TpConnection *connection;
577 gboolean sensitive = FALSE;
579 g_return_if_fail (self->priv->current_chat != NULL);
581 action = gtk_ui_manager_get_action (self->priv->ui_manager,
582 "/chats_menubar/menu_conv/menu_conv_invite_participant");
583 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
587 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
589 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
590 (tp_connection_get_status (connection, NULL) ==
591 TP_CONNECTION_STATUS_CONNECTED);
594 gtk_action_set_sensitive (action, sensitive);
598 chat_window_contact_menu_update (EmpathyChatWindow *self)
600 GtkWidget *menu, *submenu, *orig_submenu;
602 if (self->priv->updating_menu)
604 self->priv->updating_menu = TRUE;
606 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
607 "/chats_menubar/menu_contact");
608 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
610 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
612 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
616 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
617 g_object_set_data (G_OBJECT (submenu), "window", self);
619 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
620 gtk_widget_show (menu);
621 gtk_widget_set_sensitive (menu, TRUE);
625 gtk_widget_set_sensitive (menu, FALSE);
630 tp_g_signal_connect_object (orig_submenu,
632 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
635 self->priv->updating_menu = FALSE;
639 get_all_unread_messages (EmpathyChatWindow *self)
644 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
645 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
651 get_window_title_name (EmpathyChatWindow *self)
653 gchar *active_name, *ret;
655 guint current_unread_msgs;
657 nb_chats = g_list_length (self->priv->chats);
658 g_assert (nb_chats > 0);
660 active_name = empathy_chat_dup_name (self->priv->current_chat);
662 current_unread_msgs = empathy_chat_get_nb_unread_messages (
663 self->priv->current_chat);
668 if (current_unread_msgs == 0)
669 ret = g_strdup (active_name);
671 ret = g_strdup_printf (ngettext (
673 "%s (%d unread)", current_unread_msgs),
674 active_name, current_unread_msgs);
678 guint nb_others = nb_chats - 1;
679 guint all_unread_msgs;
681 all_unread_msgs = get_all_unread_messages (self);
683 if (all_unread_msgs == 0)
685 /* no unread message */
686 ret = g_strdup_printf (ngettext (
688 "%s (and %u others)", nb_others),
689 active_name, nb_others);
691 else if (all_unread_msgs == current_unread_msgs)
693 /* unread messages are in the current tab */
694 ret = g_strdup_printf (ngettext (
696 "%s (%d unread)", current_unread_msgs),
697 active_name, current_unread_msgs);
699 else if (current_unread_msgs == 0)
701 /* unread messages are in other tabs */
702 ret = g_strdup_printf (ngettext (
703 "%s (%d unread from others)",
704 "%s (%d unread from others)",
706 active_name, all_unread_msgs);
710 /* unread messages are in all the tabs */
711 ret = g_strdup_printf (ngettext (
712 "%s (%d unread from all)",
713 "%s (%d unread from all)",
715 active_name, all_unread_msgs);
719 g_free (active_name);
725 chat_window_title_update (EmpathyChatWindow *self)
729 name = get_window_title_name (self);
730 gtk_window_set_title (GTK_WINDOW (self), name);
735 chat_window_icon_update (EmpathyChatWindow *self,
736 gboolean new_messages)
739 EmpathyContact *remote_contact;
740 gboolean avatar_in_icon;
743 n_chats = g_list_length (self->priv->chats);
745 /* Update window icon */
748 gtk_window_set_icon_name (GTK_WINDOW (self),
749 EMPATHY_IMAGE_MESSAGE);
753 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
754 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
756 if (n_chats == 1 && avatar_in_icon)
758 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
759 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
761 gtk_window_set_icon (GTK_WINDOW (self), icon);
764 g_object_unref (icon);
768 gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
774 chat_window_close_button_update (EmpathyChatWindow *self,
778 GtkWidget *chat_close_button;
783 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
784 chat_close_button = g_object_get_data (G_OBJECT (chat),
785 "chat-window-tab-close-button");
786 gtk_widget_hide (chat_close_button);
790 for (i=0; i<num_pages; i++)
792 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
793 chat_close_button = g_object_get_data (G_OBJECT (chat),
794 "chat-window-tab-close-button");
795 gtk_widget_show (chat_close_button);
801 chat_window_update (EmpathyChatWindow *self,
802 gboolean update_contact_menu)
806 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
808 /* Update Tab menu */
809 chat_window_menu_context_update (self, num_pages);
811 chat_window_conversation_menu_update (self);
813 /* If this update is due to a focus-in event, we know the menu will be
814 the same as when we last left it, so no work to do. Besides, if we
815 swap out the menu on a focus-in, we may confuse any external global
817 if (update_contact_menu)
819 chat_window_contact_menu_update (self);
822 chat_window_title_update (self);
824 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
826 chat_window_close_button_update (self, num_pages);
830 append_markup_printf (GString *string,
837 va_start (args, format);
839 tmp = g_markup_vprintf_escaped (format, args);
840 g_string_append (string, tmp);
847 chat_window_update_chat_tab_full (EmpathyChat *chat,
848 gboolean update_contact_menu)
850 EmpathyChatWindow *self;
851 EmpathyContact *remote_contact;
855 const gchar *subject;
856 const gchar *status = NULL;
860 const gchar *icon_name;
861 GtkWidget *tab_image;
862 GtkWidget *menu_image;
863 GtkWidget *sending_spinner;
866 self = chat_window_find_chat (chat);
870 /* Get information */
871 name = empathy_chat_dup_name (chat);
872 account = empathy_chat_get_account (chat);
873 subject = empathy_chat_get_subject (chat);
874 remote_contact = empathy_chat_get_remote_contact (chat);
876 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
878 name, tp_proxy_get_object_path (account), subject, remote_contact);
880 /* Update tab image */
881 if (empathy_chat_get_tp_chat (chat) == NULL)
883 /* No TpChat, we are disconnected */
886 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
888 icon_name = EMPATHY_IMAGE_MESSAGE;
890 else if (remote_contact && empathy_chat_is_composing (chat))
892 icon_name = EMPATHY_IMAGE_TYPING;
894 else if (empathy_chat_is_sms_channel (chat))
896 icon_name = EMPATHY_IMAGE_SMS;
898 else if (remote_contact)
900 icon_name = empathy_icon_name_for_contact (remote_contact);
904 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
907 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
908 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
910 if (icon_name != NULL)
912 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
914 gtk_widget_show (tab_image);
915 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
917 gtk_widget_show (menu_image);
921 gtk_widget_hide (tab_image);
922 gtk_widget_hide (menu_image);
925 /* Update the sending spinner */
926 nb_sending = empathy_chat_get_n_messages_sending (chat);
927 sending_spinner = g_object_get_data (G_OBJECT (chat),
928 "chat-window-tab-sending-spinner");
930 g_object_set (sending_spinner,
931 "active", nb_sending > 0,
932 "visible", nb_sending > 0,
935 /* Update tab tooltip */
936 tooltip = g_string_new (NULL);
940 id = empathy_contact_get_id (remote_contact);
941 status = empathy_contact_get_presence_message (remote_contact);
948 if (empathy_chat_is_sms_channel (chat))
949 append_markup_printf (tooltip, "%s ", _("SMS:"));
951 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
952 id, tp_account_get_display_name (account));
956 char *tmp = g_strdup_printf (
957 ngettext ("Sending %d message",
958 "Sending %d messages",
962 g_string_append (tooltip, "\n");
963 g_string_append (tooltip, tmp);
965 gtk_widget_set_tooltip_text (sending_spinner, tmp);
969 if (!TPAW_STR_EMPTY (status))
970 append_markup_printf (tooltip, "\n<i>%s</i>", status);
972 if (!TPAW_STR_EMPTY (subject))
973 append_markup_printf (tooltip, "\n<b>%s</b> %s",
974 _("Topic:"), subject);
976 if (remote_contact && empathy_chat_is_composing (chat))
977 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
979 if (remote_contact != NULL)
981 const gchar * const *types;
983 types = empathy_contact_get_client_types (remote_contact);
984 if (empathy_client_types_contains_mobile_device ((GStrv) types))
986 /* I'm on a mobile device ! */
989 name = g_strdup_printf ("☎ %s", name);
994 markup = g_string_free (tooltip, FALSE);
995 widget = g_object_get_data (G_OBJECT (chat),
996 "chat-window-tab-tooltip-widget");
997 gtk_widget_set_tooltip_markup (widget, markup);
999 widget = g_object_get_data (G_OBJECT (chat),
1000 "chat-window-menu-tooltip-widget");
1001 gtk_widget_set_tooltip_markup (widget, markup);
1004 /* Update tab and menu label */
1005 if (empathy_chat_is_highlighted (chat))
1007 markup = g_markup_printf_escaped (
1008 "<span color=\"red\" weight=\"bold\">%s</span>",
1013 markup = g_markup_escape_text (name, -1);
1016 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1017 gtk_label_set_markup (GTK_LABEL (widget), markup);
1018 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1019 gtk_label_set_markup (GTK_LABEL (widget), markup);
1022 /* Update the window if it's the current chat */
1023 if (self->priv->current_chat == chat)
1024 chat_window_update (self, update_contact_menu);
1030 chat_window_update_chat_tab (EmpathyChat *chat)
1032 chat_window_update_chat_tab_full (chat, TRUE);
1036 chat_window_chat_notify_cb (EmpathyChat *chat)
1038 EmpathyChatWindow *window;
1039 EmpathyContact *old_remote_contact;
1040 EmpathyContact *remote_contact = NULL;
1042 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1043 "chat-window-remote-contact");
1044 remote_contact = empathy_chat_get_remote_contact (chat);
1046 if (old_remote_contact != remote_contact)
1048 /* The remote-contact associated with the chat changed, we need
1049 * to keep track of any change of that contact and update the
1050 * window each time. */
1052 g_signal_connect_swapped (remote_contact, "notify",
1053 G_CALLBACK (chat_window_update_chat_tab), chat);
1055 if (old_remote_contact)
1056 g_signal_handlers_disconnect_by_func (old_remote_contact,
1057 chat_window_update_chat_tab, chat);
1059 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1060 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1063 chat_window_update_chat_tab (chat);
1065 window = chat_window_find_chat (chat);
1067 chat_window_update (window, FALSE);
1071 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1072 EmpathySmiley *smiley,
1075 EmpathyChatWindow *self = user_data;
1077 GtkTextBuffer *buffer;
1080 chat = self->priv->current_chat;
1082 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1083 gtk_text_buffer_get_end_iter (buffer, &iter);
1084 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1088 chat_window_conv_activate_cb (GtkAction *action,
1089 EmpathyChatWindow *self)
1093 EmpathyContact *remote_contact = NULL;
1094 gboolean disconnected;
1096 /* Favorite room menu */
1097 is_room = empathy_chat_is_room (self->priv->current_chat);
1102 gboolean found = FALSE;
1103 EmpathyChatroom *chatroom;
1105 room = empathy_chat_get_id (self->priv->current_chat);
1106 account = empathy_chat_get_account (self->priv->current_chat);
1107 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1110 if (chatroom != NULL)
1111 found = empathy_chatroom_is_favorite (chatroom);
1113 DEBUG ("This room %s favorite", found ? "is" : "is not");
1114 gtk_toggle_action_set_active (
1115 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1117 if (chatroom != NULL)
1118 found = empathy_chatroom_is_always_urgent (chatroom);
1120 gtk_toggle_action_set_active (
1121 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1124 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1125 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1127 /* Show contacts menu */
1128 g_object_get (self->priv->current_chat,
1129 "remote-contact", &remote_contact,
1130 "show-contacts", &active,
1133 if (remote_contact == NULL)
1135 gtk_toggle_action_set_active (
1136 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1139 /* Menu-items to be visible for MUCs only */
1140 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1141 (remote_contact == NULL));
1143 disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1146 gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1147 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1151 TpChannel *channel = NULL;
1152 TpContact *self_contact = NULL;
1153 TpHandle self_handle = 0;
1155 channel = (TpChannel *) (empathy_chat_get_tp_chat (
1156 self->priv->current_chat));
1157 self_contact = tp_channel_group_get_self_contact (channel);
1158 if (self_contact == NULL)
1160 /* The channel may not be a group */
1161 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1165 self_handle = tp_contact_get_handle (self_contact);
1166 /* There is sometimes a lag between the members-changed signal
1167 emitted on tp-chat and invalidated signal being emitted on the channel.
1168 Leave Chat menu-item should be sensitive only till our self-handle is
1169 a part of channel-members */
1170 gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1174 /* Join Chat is insensitive for a connected chat */
1175 gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1178 if (remote_contact != NULL)
1179 g_object_unref (remote_contact);
1183 chat_window_clear_activate_cb (GtkAction *action,
1184 EmpathyChatWindow *self)
1186 empathy_chat_clear (self->priv->current_chat);
1190 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1191 EmpathyChatWindow *self)
1197 EmpathyChatroom *chatroom;
1199 active = gtk_toggle_action_get_active (toggle_action);
1200 account = empathy_chat_get_account (self->priv->current_chat);
1201 room = empathy_chat_get_id (self->priv->current_chat);
1202 name = empathy_chat_dup_name (self->priv->current_chat);
1204 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1205 account, room, name);
1207 empathy_chatroom_set_favorite (chatroom, active);
1208 g_object_unref (chatroom);
1213 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1214 EmpathyChatWindow *self)
1220 EmpathyChatroom *chatroom;
1222 active = gtk_toggle_action_get_active (toggle_action);
1223 account = empathy_chat_get_account (self->priv->current_chat);
1224 room = empathy_chat_get_id (self->priv->current_chat);
1225 name = empathy_chat_dup_name (self->priv->current_chat);
1227 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1228 account, room, name);
1230 empathy_chatroom_set_always_urgent (chatroom, active);
1231 g_object_unref (chatroom);
1236 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1237 EmpathyChatWindow *self)
1241 active = gtk_toggle_action_get_active (toggle_action);
1243 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1247 chat_window_invite_participant_activate_cb (GtkAction *action,
1248 EmpathyChatWindow *self)
1251 EmpathyTpChat *tp_chat;
1254 g_return_if_fail (self->priv->current_chat != NULL);
1256 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1258 dialog = empathy_invite_participant_dialog_new (
1259 GTK_WINDOW (self), tp_chat);
1261 gtk_widget_show (dialog);
1263 response = gtk_dialog_run (GTK_DIALOG (dialog));
1265 if (response == GTK_RESPONSE_ACCEPT)
1267 TpContact *tp_contact;
1268 EmpathyContact *contact;
1270 tp_contact = empathy_invite_participant_dialog_get_selected (
1271 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1272 if (tp_contact == NULL)
1275 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1277 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1279 g_object_unref (contact);
1283 gtk_widget_destroy (dialog);
1287 chat_window_join_chat_activate_cb (GtkAction *action,
1288 EmpathyChatWindow *self)
1290 g_return_if_fail (self->priv->current_chat != NULL);
1292 empathy_chat_join_muc (self->priv->current_chat,
1293 empathy_chat_get_id (self->priv->current_chat));
1297 chat_window_leave_chat_activate_cb (GtkAction *action,
1298 EmpathyChatWindow *self)
1300 EmpathyTpChat * tp_chat;
1302 g_return_if_fail (self->priv->current_chat != NULL);
1304 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1305 if (tp_chat != NULL)
1306 empathy_tp_chat_leave (tp_chat, "");
1310 chat_window_close_activate_cb (GtkAction *action,
1311 EmpathyChatWindow *self)
1313 g_return_if_fail (self->priv->current_chat != NULL);
1315 maybe_close_chat (self, self->priv->current_chat);
1319 chat_window_edit_activate_cb (GtkAction *action,
1320 EmpathyChatWindow *self)
1322 GtkClipboard *clipboard;
1323 GtkTextBuffer *buffer;
1324 gboolean text_available;
1326 g_return_if_fail (self->priv->current_chat != NULL);
1328 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1330 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1331 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1332 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1336 buffer = gtk_text_view_get_buffer (
1337 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1339 if (gtk_text_buffer_get_has_selection (buffer))
1341 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1342 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1348 selection = empathy_theme_adium_get_has_selection (
1349 self->priv->current_chat->view);
1351 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1352 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1355 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1356 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1357 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1361 chat_window_cut_activate_cb (GtkAction *action,
1362 EmpathyChatWindow *self)
1364 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1366 empathy_chat_cut (self->priv->current_chat);
1370 chat_window_copy_activate_cb (GtkAction *action,
1371 EmpathyChatWindow *self)
1373 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1375 empathy_chat_copy (self->priv->current_chat);
1379 chat_window_paste_activate_cb (GtkAction *action,
1380 EmpathyChatWindow *self)
1382 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1384 empathy_chat_paste (self->priv->current_chat);
1388 chat_window_find_activate_cb (GtkAction *action,
1389 EmpathyChatWindow *self)
1391 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1393 empathy_chat_find (self->priv->current_chat);
1397 chat_window_tabs_next_activate_cb (GtkAction *action,
1398 EmpathyChatWindow *self)
1400 gint index_, numPages;
1401 gboolean wrap_around;
1403 g_object_get (gtk_settings_get_default (),
1404 "gtk-keynav-wrap-around", &wrap_around,
1407 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1408 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1410 if (index_ == (numPages - 1) && wrap_around)
1412 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1416 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1420 chat_window_tabs_previous_activate_cb (GtkAction *action,
1421 EmpathyChatWindow *self)
1423 gint index_, numPages;
1424 gboolean wrap_around;
1426 g_object_get (gtk_settings_get_default (),
1427 "gtk-keynav-wrap-around", &wrap_around,
1430 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1431 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1433 if (index_ <= 0 && wrap_around)
1435 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1440 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1444 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1445 EmpathyChatWindow *self)
1447 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1448 empathy_get_current_action_time ());
1452 chat_window_tabs_left_activate_cb (GtkAction *action,
1453 EmpathyChatWindow *self)
1456 gint index_, num_pages;
1458 chat = self->priv->current_chat;
1459 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1463 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1466 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1467 chat_window_menu_context_update (self, num_pages);
1471 chat_window_tabs_right_activate_cb (GtkAction *action,
1472 EmpathyChatWindow *self)
1475 gint index_, num_pages;
1477 chat = self->priv->current_chat;
1478 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1480 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1483 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1484 chat_window_menu_context_update (self, num_pages);
1487 static EmpathyChatWindow *
1488 empathy_chat_window_new (void)
1490 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1491 "default-width", 580,
1492 "default-height", 480,
1499 chat_window_detach_activate_cb (GtkAction *action,
1500 EmpathyChatWindow *self)
1502 EmpathyChatWindow *new_window;
1505 chat = self->priv->current_chat;
1506 new_window = empathy_chat_window_new ();
1508 empathy_chat_window_move_chat (self, new_window, chat);
1510 gtk_widget_show (GTK_WIDGET (new_window));
1514 chat_window_help_contents_activate_cb (GtkAction *action,
1515 EmpathyChatWindow *self)
1517 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1521 chat_window_help_about_activate_cb (GtkAction *action,
1522 EmpathyChatWindow *self)
1524 empathy_about_dialog_new (GTK_WINDOW (self));
1528 chat_window_delete_event_cb (GtkWidget *dialog,
1530 EmpathyChatWindow *self)
1532 EmpathyChat *chat = NULL;
1536 DEBUG ("Delete event received");
1538 for (l = self->priv->chats; l != NULL; l = l->next)
1540 if (chat_needs_close_confirmation (l->data))
1549 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1553 remove_all_chats (self);
1560 chat_window_composing_cb (EmpathyChat *chat,
1561 gboolean is_composing,
1562 EmpathyChatWindow *self)
1564 chat_window_update_chat_tab (chat);
1568 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1571 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1575 chat_window_notification_closed_cb (NotifyNotification *notify,
1576 EmpathyChatWindow *self)
1578 g_object_unref (notify);
1579 if (self->priv->notification == notify)
1580 self->priv->notification = NULL;
1584 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1585 EmpathyMessage *message,
1588 EmpathyContact *sender;
1589 const gchar *header;
1593 gboolean res, has_x_canonical_append;
1594 NotifyNotification *notification = self->priv->notification;
1596 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1599 res = g_settings_get_boolean (self->priv->gsettings_notif,
1600 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1605 sender = empathy_message_get_sender (message);
1606 header = empathy_contact_get_alias (sender);
1607 body = empathy_message_get_body (message);
1608 escaped = g_markup_escape_text (body, -1);
1610 has_x_canonical_append = empathy_notify_manager_has_capability (
1611 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1613 if (notification != NULL && !has_x_canonical_append)
1615 /* if the notification server supports x-canonical-append, it is
1616 better to not use notify_notification_update to avoid
1617 overwriting the current notification message */
1618 notify_notification_update (notification,
1619 header, escaped, NULL);
1623 /* if the notification server supports x-canonical-append,
1624 the hint will be added, so that the message from the
1625 just created notification will be automatically appended
1626 to an existing notification with the same title.
1627 In this way the previous message will not be lost: the new
1628 message will appear below it, in the same notification */
1629 const gchar *category = empathy_chat_is_room (chat)
1630 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1631 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1633 notification = empathy_notify_manager_create_notification (header,
1636 if (self->priv->notification == NULL)
1637 self->priv->notification = notification;
1639 tp_g_signal_connect_object (notification, "closed",
1640 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1642 if (has_x_canonical_append)
1644 /* We have to set a not empty string to keep libnotify happy */
1645 notify_notification_set_hint_string (notification,
1646 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1649 notify_notification_set_hint (notification,
1650 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1653 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1654 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1658 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1659 g_object_unref (pixbuf);
1662 notify_notification_show (notification, NULL);
1668 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1672 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1674 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1680 chat_window_new_message_cb (EmpathyChat *chat,
1681 EmpathyMessage *message,
1683 gboolean should_highlight,
1684 EmpathyChatWindow *self)
1687 gboolean needs_urgency;
1688 EmpathyContact *sender;
1690 has_focus = empathy_chat_window_has_focus (self);
1692 /* - if we're the sender, we play the sound if it's specified in the
1693 * preferences and we're not away.
1694 * - if we receive a message, we play the sound if it's specified in the
1695 * preferences and the window does not have focus on the chat receiving
1699 sender = empathy_message_get_sender (message);
1701 if (empathy_contact_is_user (sender))
1703 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1704 EMPATHY_SOUND_MESSAGE_OUTGOING);
1708 if (has_focus && self->priv->current_chat == chat)
1710 /* window and tab are focused so consider the message to be read */
1712 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1713 empathy_chat_messages_read (chat);
1717 /* Update the chat tab if this is the first unread message */
1718 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1720 chat_window_update_chat_tab (chat);
1723 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1724 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1725 * an unamed MUC (msn-like).
1726 * In case of a MUC, we set urgency if either:
1727 * a) the chatroom's always_urgent property is TRUE
1728 * b) the message contains our alias
1730 if (empathy_chat_is_room (chat))
1734 EmpathyChatroom *chatroom;
1736 account = empathy_chat_get_account (chat);
1737 room = empathy_chat_get_id (chat);
1739 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1742 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1743 needs_urgency = TRUE;
1745 needs_urgency = should_highlight;
1749 needs_urgency = TRUE;
1755 chat_window_set_urgency_hint (self, TRUE);
1757 /* Pending messages have already been displayed and notified in the
1758 * approver, so we don't display a notification and play a sound
1762 empathy_sound_manager_play (self->priv->sound_mgr,
1763 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1765 chat_window_show_or_update_notification (self, message, chat);
1769 /* update the number of unread messages and the window icon */
1770 chat_window_title_update (self);
1771 chat_window_icon_update (self, TRUE);
1775 chat_window_command_part (EmpathyChat *chat,
1778 EmpathyChat *chat_to_be_parted;
1779 EmpathyTpChat *tp_chat = NULL;
1781 if (strv[1] == NULL)
1783 /* No chatroom ID specified */
1784 tp_chat = empathy_chat_get_tp_chat (chat);
1787 empathy_tp_chat_leave (tp_chat, "");
1792 chat_to_be_parted = empathy_chat_window_find_chat (
1793 empathy_chat_get_account (chat), strv[1], FALSE);
1795 if (chat_to_be_parted != NULL)
1797 /* Found a chatroom matching the specified ID */
1798 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1801 empathy_tp_chat_leave (tp_chat, strv[2]);
1807 /* Going by the syntax of PART command:
1809 * /PART [<chatroom-ID>] [<reason>]
1811 * Chatroom-ID is not a must to specify a reason.
1812 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1813 * MUC then the current chatroom should be parted and srtv[1] should
1814 * be treated as part of the optional part-message. */
1815 message = g_strconcat (strv[1], " ", strv[2], NULL);
1816 tp_chat = empathy_chat_get_tp_chat (chat);
1819 empathy_tp_chat_leave (tp_chat, message);
1825 static GtkNotebook *
1826 notebook_create_window_cb (GtkNotebook *source,
1832 EmpathyChatWindow *window, *new_window;
1835 chat = EMPATHY_CHAT (page);
1836 window = chat_window_find_chat (chat);
1838 new_window = empathy_chat_window_new ();
1840 DEBUG ("Detach hook called");
1842 empathy_chat_window_move_chat (window, new_window, chat);
1844 gtk_widget_show (GTK_WIDGET (new_window));
1845 gtk_window_move (GTK_WINDOW (new_window), x, y);
1851 chat_window_page_switched_cb (GtkNotebook *notebook,
1854 EmpathyChatWindow *self)
1856 EmpathyChat *chat = EMPATHY_CHAT (child);
1858 DEBUG ("Page switched");
1860 if (self->priv->page_added)
1862 self->priv->page_added = FALSE;
1863 empathy_chat_scroll_down (chat);
1865 else if (self->priv->current_chat == chat)
1870 self->priv->current_chat = chat;
1871 empathy_chat_messages_read (chat);
1873 chat_window_update_chat_tab (chat);
1877 chat_window_page_added_cb (GtkNotebook *notebook,
1880 EmpathyChatWindow *self)
1884 /* If we just received DND to the same window, we don't want
1885 * to do anything here like removing the tab and then readding
1886 * it, so we return here and in "page-added".
1888 if (self->priv->dnd_same_window)
1890 DEBUG ("Page added (back to the same window)");
1891 self->priv->dnd_same_window = FALSE;
1895 DEBUG ("Page added");
1897 /* Get chat object */
1898 chat = EMPATHY_CHAT (child);
1900 /* Connect chat signals for this window */
1901 g_signal_connect (chat, "composing",
1902 G_CALLBACK (chat_window_composing_cb), self);
1903 g_signal_connect (chat, "new-message",
1904 G_CALLBACK (chat_window_new_message_cb), self);
1905 g_signal_connect (chat, "part-command-entered",
1906 G_CALLBACK (chat_window_command_part), NULL);
1907 g_signal_connect (chat, "notify::tp-chat",
1908 G_CALLBACK (chat_window_update_chat_tab), self);
1910 /* Set flag so we know to perform some special operations on
1911 * switch page due to the new page being added.
1913 self->priv->page_added = TRUE;
1915 /* Get list of chats up to date */
1916 self->priv->chats = g_list_append (self->priv->chats, chat);
1918 chat_window_update_chat_tab (chat);
1922 chat_window_page_removed_cb (GtkNotebook *notebook,
1925 EmpathyChatWindow *self)
1929 /* If we just received DND to the same window, we don't want
1930 * to do anything here like removing the tab and then readding
1931 * it, so we return here and in "page-added".
1933 if (self->priv->dnd_same_window)
1935 DEBUG ("Page removed (and will be readded to same window)");
1939 DEBUG ("Page removed");
1941 /* Get chat object */
1942 chat = EMPATHY_CHAT (child);
1944 /* Disconnect all signal handlers for this chat and this window */
1945 g_signal_handlers_disconnect_by_func (chat,
1946 G_CALLBACK (chat_window_composing_cb), self);
1947 g_signal_handlers_disconnect_by_func (chat,
1948 G_CALLBACK (chat_window_new_message_cb), self);
1949 g_signal_handlers_disconnect_by_func (chat,
1950 G_CALLBACK (chat_window_update_chat_tab), self);
1952 /* Keep list of chats up to date */
1953 self->priv->chats = g_list_remove (self->priv->chats, chat);
1954 empathy_chat_messages_read (chat);
1956 if (self->priv->chats == NULL)
1958 gtk_widget_destroy (GTK_WIDGET (self));
1962 chat_window_update (self, TRUE);
1967 chat_window_focus_in_event_cb (GtkWidget *widget,
1969 EmpathyChatWindow *self)
1971 empathy_chat_messages_read (self->priv->current_chat);
1973 chat_window_set_urgency_hint (self, FALSE);
1975 /* Update the title, since we now mark all unread messages as read. */
1976 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1982 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1983 EmpathyChatWindow *self)
1985 chat_window_contact_menu_update (self);
1989 chat_window_focus_out_event_cb (GtkWidget *widget,
1991 EmpathyChatWindow *self)
1993 if (self->priv->individual_mgr != NULL)
1996 /* Keep the individual manager alive so we won't fetch everything from Folks
1997 * each time we need to use it. Loading FolksAggregator can takes quite a
1998 * while (if user has a huge LDAP abook for example) and it blocks
1999 * the mainloop during most of this loading. We workaround this by loading
2000 * it when the chat window has been unfocused and so, hopefully, not impact
2001 * the reactivity of the chat window too much.
2003 * The individual manager (and so Folks) is needed to know to which
2004 * FolksIndividual a TpContact belongs, including:
2005 * - empathy_chat_get_contact_menu: to list all the personas of the contact
2006 * - empathy_display_individual_info: to invoke gnome-contacts with the
2007 * FolksIndividual.id of the contact
2008 * - drag_data_received_individual_id: to find the individual associated
2009 * with the ID we received from the DnD in order to invite him.
2011 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2013 if (!empathy_individual_manager_get_contacts_loaded (
2014 self->priv->individual_mgr))
2016 /* We want to update the contact menu when Folks is loaded so we can
2017 * list all the personas of the contact. */
2018 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2019 G_CALLBACK (contacts_loaded_cb), self, 0);
2022 g_object_notify (G_OBJECT (self), "individual-manager");
2028 chat_window_drag_drop (GtkWidget *widget,
2029 GdkDragContext *context,
2033 EmpathyChatWindow *self)
2037 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2038 if (target == GDK_NONE)
2039 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2041 if (target != GDK_NONE)
2043 gtk_drag_get_data (widget, context, target, time_);
2051 chat_window_drag_motion (GtkWidget *widget,
2052 GdkDragContext *context,
2056 EmpathyChatWindow *self)
2060 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2062 if (target != GDK_NONE)
2064 /* This is a file drag. Ensure the contact is online and set the
2065 drag type to COPY. Note that it's possible that the tab will
2066 be switched by GTK+ after a timeout from drag_motion without
2067 getting another drag_motion to disable the drop. You have
2068 to hold your mouse really still.
2070 EmpathyContact *contact;
2072 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2074 /* contact is NULL for multi-user chats. We don't do
2075 * file transfers to MUCs. We also don't send files
2076 * to offline contacts or contacts that don't support
2079 if ((contact == NULL) || !empathy_contact_is_online (contact))
2081 gdk_drag_status (context, 0, time_);
2085 if (!(empathy_contact_get_capabilities (contact)
2086 & EMPATHY_CAPABILITIES_FT))
2088 gdk_drag_status (context, 0, time_);
2092 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2096 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2097 if (target != GDK_NONE)
2099 /* This is a drag of a contact from a contact list. Set to COPY.
2100 FIXME: If this drag is to a MUC window, it invites the user.
2101 Otherwise, it opens a chat. Should we use a different drag
2102 type for invites? Should we allow ASK?
2104 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2112 drag_data_received_individual_id (EmpathyChatWindow *self,
2114 GdkDragContext *context,
2117 GtkSelectionData *selection,
2122 FolksIndividual *individual;
2123 EmpathyTpChat *chat;
2124 TpContact *tp_contact;
2126 EmpathyContact *contact;
2128 id = (const gchar *) gtk_selection_data_get_data (selection);
2130 DEBUG ("DND invididual %s", id);
2132 if (self->priv->current_chat == NULL)
2135 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2139 if (!empathy_tp_chat_can_add_contact (chat))
2141 DEBUG ("Can't invite contact to %s",
2142 tp_proxy_get_object_path (chat));
2146 if (self->priv->individual_mgr == NULL)
2147 /* Not likely as we have to focus out the chat window in order to start
2148 * the DnD but best to be safe. */
2151 individual = empathy_individual_manager_lookup_member (
2152 self->priv->individual_mgr, id);
2153 if (individual == NULL)
2155 DEBUG ("Failed to find individual %s", id);
2159 conn = tp_channel_get_connection ((TpChannel *) chat);
2160 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2161 if (tp_contact == NULL)
2163 DEBUG ("Can't find a TpContact on connection %s for %s",
2164 tp_proxy_get_object_path (conn), id);
2168 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2169 tp_channel_get_identifier ((TpChannel *) chat));
2171 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2172 empathy_tp_chat_add (chat, contact, NULL);
2173 g_object_unref (contact);
2176 gtk_drag_finish (context, TRUE, FALSE, time_);
2180 chat_window_drag_data_received (GtkWidget *widget,
2181 GdkDragContext *context,
2184 GtkSelectionData *selection,
2187 EmpathyChatWindow *self)
2189 if (info == DND_DRAG_TYPE_CONTACT_ID)
2191 EmpathyChat *chat = NULL;
2192 EmpathyChatWindow *old_window;
2193 TpAccount *account = NULL;
2194 EmpathyClientFactory *factory;
2197 const gchar *account_id;
2198 const gchar *contact_id;
2200 id = (const gchar*) gtk_selection_data_get_data (selection);
2202 factory = empathy_client_factory_dup ();
2204 DEBUG ("DND contact from roster with id:'%s'", id);
2206 strv = g_strsplit (id, ":", 2);
2207 if (g_strv_length (strv) == 2)
2209 account_id = strv[0];
2210 contact_id = strv[1];
2212 account = tp_simple_client_factory_ensure_account (
2213 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2215 g_object_unref (factory);
2216 if (account != NULL)
2217 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2220 if (account == NULL)
2223 gtk_drag_finish (context, FALSE, FALSE, time_);
2229 empathy_chat_with_contact_id (account, contact_id,
2230 empathy_get_current_action_time (), NULL, NULL);
2238 old_window = chat_window_find_chat (chat);
2241 if (old_window == self)
2243 gtk_drag_finish (context, TRUE, FALSE, time_);
2247 empathy_chat_window_move_chat (old_window, self, chat);
2251 empathy_chat_window_add_chat (self, chat);
2254 /* Added to take care of any outstanding chat events */
2255 empathy_chat_window_present_chat (chat,
2256 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2258 /* We should return TRUE to remove the data when doing
2259 * GDK_ACTION_MOVE, but we don't here otherwise it has
2260 * weird consequences, and we handle that internally
2261 * anyway with add_chat () and remove_chat ().
2263 gtk_drag_finish (context, TRUE, FALSE, time_);
2265 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2267 drag_data_received_individual_id (self, widget, context, x, y,
2268 selection, info, time_);
2270 else if (info == DND_DRAG_TYPE_URI_LIST)
2272 EmpathyContact *contact;
2275 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2277 /* contact is NULL when current_chat is a multi-user chat.
2278 * We don't do file transfers to MUCs, so just cancel the drag.
2280 if (contact == NULL)
2282 gtk_drag_finish (context, TRUE, FALSE, time_);
2286 data = (const gchar *) gtk_selection_data_get_data (selection);
2287 empathy_send_file_from_uri_list (contact, data);
2289 gtk_drag_finish (context, TRUE, FALSE, time_);
2291 else if (info == DND_DRAG_TYPE_TAB)
2294 EmpathyChatWindow *old_window = NULL;
2298 chat = (void *) gtk_selection_data_get_data (selection);
2299 old_window = chat_window_find_chat (*chat);
2303 self->priv->dnd_same_window = (old_window == self);
2305 DEBUG ("DND tab (within same window: %s)",
2306 self->priv->dnd_same_window ? "Yes" : "No");
2311 DEBUG ("DND from unknown source");
2312 gtk_drag_finish (context, FALSE, FALSE, time_);
2317 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2318 guint num_chats_in_manager,
2319 EmpathyChatWindow *self)
2321 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2322 num_chats_in_manager > 0);
2326 chat_window_finalize (GObject *object)
2328 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2330 DEBUG ("Finalized: %p", object);
2332 g_object_unref (self->priv->ui_manager);
2333 g_object_unref (self->priv->chatroom_manager);
2334 g_object_unref (self->priv->notify_mgr);
2335 g_object_unref (self->priv->gsettings_chat);
2336 g_object_unref (self->priv->gsettings_notif);
2337 g_object_unref (self->priv->gsettings_ui);
2338 g_object_unref (self->priv->sound_mgr);
2339 g_clear_object (&self->priv->individual_mgr);
2341 if (self->priv->notification != NULL)
2343 notify_notification_close (self->priv->notification, NULL);
2344 self->priv->notification = NULL;
2347 if (self->priv->contact_targets)
2348 gtk_target_list_unref (self->priv->contact_targets);
2350 if (self->priv->file_targets)
2351 gtk_target_list_unref (self->priv->file_targets);
2353 if (self->priv->chat_manager)
2355 g_signal_handler_disconnect (self->priv->chat_manager,
2356 self->priv->chat_manager_chats_changed_id);
2357 g_object_unref (self->priv->chat_manager);
2358 self->priv->chat_manager = NULL;
2361 chat_windows = g_list_remove (chat_windows, self);
2363 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2367 chat_window_get_property (GObject *object,
2372 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2374 switch (property_id)
2376 case PROP_INDIVIDUAL_MGR:
2377 g_value_set_object (value, self->priv->individual_mgr);
2379 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2385 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2387 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2390 object_class->get_property = chat_window_get_property;
2391 object_class->finalize = chat_window_finalize;
2393 spec = g_param_spec_object ("individual-manager", "individual-manager",
2394 "EmpathyIndividualManager",
2395 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2396 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2397 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2399 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2403 empathy_chat_window_init (EmpathyChatWindow *self)
2406 GtkAccelGroup *accel_group;
2411 GtkWidget *chat_vbox;
2413 EmpathySmileyManager *smiley_manager;
2415 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2416 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2418 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2419 gui = tpaw_builder_get_file (filename,
2420 "chat_vbox", &chat_vbox,
2421 "ui_manager", &self->priv->ui_manager,
2422 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2423 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2424 "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2425 "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2426 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2427 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2428 "menu_edit_cut", &self->priv->menu_edit_cut,
2429 "menu_edit_copy", &self->priv->menu_edit_copy,
2430 "menu_edit_paste", &self->priv->menu_edit_paste,
2431 "menu_edit_find", &self->priv->menu_edit_find,
2432 "menu_tabs_next", &self->priv->menu_tabs_next,
2433 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2434 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2435 "menu_tabs_left", &self->priv->menu_tabs_left,
2436 "menu_tabs_right", &self->priv->menu_tabs_right,
2437 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2441 tpaw_builder_connect (gui, self,
2442 "menu_conv", "activate", chat_window_conv_activate_cb,
2443 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2444 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2445 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2446 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2447 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2448 "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2449 "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2450 "menu_conv_close", "activate", chat_window_close_activate_cb,
2451 "menu_edit", "activate", chat_window_edit_activate_cb,
2452 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2453 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2454 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2455 "menu_edit_find", "activate", chat_window_find_activate_cb,
2456 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2457 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2458 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2459 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2460 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2461 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2462 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2463 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2466 empathy_set_css_provider (GTK_WIDGET (self));
2468 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2469 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2470 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2471 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2473 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2475 self->priv->notebook = gtk_notebook_new ();
2477 g_signal_connect (self->priv->notebook, "create-window",
2478 G_CALLBACK (notebook_create_window_cb), self);
2480 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2482 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2483 "EmpathyChatWindow");
2484 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2485 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2486 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2487 gtk_widget_show (self->priv->notebook);
2490 accel_group = gtk_accel_group_new ();
2491 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2493 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2495 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2498 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2502 g_object_unref (accel_group);
2504 /* Set up drag target lists */
2505 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2506 G_N_ELEMENTS (drag_types_dest_contact));
2508 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2509 G_N_ELEMENTS (drag_types_dest_file));
2511 /* Set up smiley menu */
2512 smiley_manager = empathy_smiley_manager_dup_singleton ();
2513 submenu = empathy_smiley_menu_new (smiley_manager,
2514 chat_window_insert_smiley_activate_cb, self);
2516 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2517 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2518 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2519 g_object_unref (smiley_manager);
2521 /* Set up signals we can't do with ui file since we may need to
2522 * block/unblock them at some later stage.
2525 g_signal_connect (self, "delete_event",
2526 G_CALLBACK (chat_window_delete_event_cb), self);
2527 g_signal_connect (self, "focus_in_event",
2528 G_CALLBACK (chat_window_focus_in_event_cb), self);
2529 g_signal_connect (self, "focus_out_event",
2530 G_CALLBACK (chat_window_focus_out_event_cb), self);
2531 g_signal_connect_after (self->priv->notebook, "switch_page",
2532 G_CALLBACK (chat_window_page_switched_cb), self);
2533 g_signal_connect (self->priv->notebook, "page_added",
2534 G_CALLBACK (chat_window_page_added_cb), self);
2535 g_signal_connect (self->priv->notebook, "page_removed",
2536 G_CALLBACK (chat_window_page_removed_cb), self);
2538 /* Set up drag and drop */
2539 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2540 GTK_DEST_DEFAULT_HIGHLIGHT,
2542 G_N_ELEMENTS (drag_types_dest),
2543 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2545 /* connect_after to allow GtkNotebook's built-in tab switching */
2546 g_signal_connect_after (self->priv->notebook, "drag-motion",
2547 G_CALLBACK (chat_window_drag_motion), self);
2548 g_signal_connect (self->priv->notebook, "drag-data-received",
2549 G_CALLBACK (chat_window_drag_data_received), self);
2550 g_signal_connect (self->priv->notebook, "drag-drop",
2551 G_CALLBACK (chat_window_drag_drop), self);
2553 chat_windows = g_list_prepend (chat_windows, self);
2555 /* Set up private details */
2556 self->priv->chats = NULL;
2557 self->priv->current_chat = NULL;
2558 self->priv->notification = NULL;
2560 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2562 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2563 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2564 self->priv->chat_manager, "closed-chats-changed",
2565 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2567 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2568 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2570 g_object_ref (self->priv->ui_manager);
2571 g_object_unref (gui);
2574 /* Returns the window to open a new tab in if there is a suitable window,
2575 * otherwise, returns NULL indicating that a new window should be added.
2577 static EmpathyChatWindow *
2578 empathy_chat_window_get_default (gboolean room)
2580 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2582 gboolean separate_windows = TRUE;
2584 separate_windows = g_settings_get_boolean (gsettings,
2585 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2587 g_object_unref (gsettings);
2589 if (separate_windows)
2590 /* Always create a new window */
2593 for (l = chat_windows; l; l = l->next)
2595 EmpathyChatWindow *chat_window;
2596 guint nb_rooms, nb_private;
2598 chat_window = l->data;
2600 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2602 /* Skip the window if there aren't any rooms in it */
2603 if (room && nb_rooms == 0)
2606 /* Skip the window if there aren't any 1-1 chats in it */
2607 if (!room && nb_private == 0)
2617 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2621 GtkWidget *popup_label;
2623 GValue value = { 0, };
2625 g_return_if_fail (self != NULL);
2626 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2628 /* Reference the chat object */
2629 g_object_ref (chat);
2631 /* If this window has just been created, position it */
2632 if (self->priv->chats == NULL)
2634 const gchar *name = "chat-window";
2635 gboolean separate_windows;
2637 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2638 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2640 if (empathy_chat_is_room (chat))
2641 name = "room-window";
2643 if (separate_windows)
2647 /* Save current position of the window */
2648 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2650 /* First bind to the 'generic' name. So new window for which we didn't
2651 * save a geometry yet will have the geometry of the last saved
2652 * window (bgo #601191). */
2653 empathy_geometry_bind (GTK_WINDOW (self), name);
2655 /* Restore previous position of the window so the newly created window
2656 * won't be in the same position as the latest saved window and so
2657 * completely hide it. */
2658 gtk_window_move (GTK_WINDOW (self), x, y);
2660 /* Then bind it to the name of the contact/room so we'll save the
2661 * geometry specific to this window */
2662 name = empathy_chat_get_id (chat);
2665 empathy_geometry_bind (GTK_WINDOW (self), name);
2668 child = GTK_WIDGET (chat);
2669 label = chat_window_create_label (self, chat, TRUE);
2670 popup_label = chat_window_create_label (self, chat, FALSE);
2671 gtk_widget_show (child);
2673 g_signal_connect (chat, "notify::name",
2674 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2675 g_signal_connect (chat, "notify::subject",
2676 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2677 g_signal_connect (chat, "notify::remote-contact",
2678 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2679 g_signal_connect (chat, "notify::sms-channel",
2680 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2681 g_signal_connect (chat, "notify::n-messages-sending",
2682 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2683 g_signal_connect (chat, "notify::nb-unread-messages",
2684 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2685 chat_window_chat_notify_cb (chat);
2687 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2689 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2690 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2691 g_value_init (&value, G_TYPE_BOOLEAN);
2692 g_value_set_boolean (&value, TRUE);
2693 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2694 child, "tab-expand" , &value);
2695 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2696 child, "tab-fill" , &value);
2697 g_value_unset (&value);
2699 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2703 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2707 EmpathyContact *remote_contact;
2708 EmpathyChatManager *chat_manager;
2710 g_return_if_fail (self != NULL);
2711 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2713 g_signal_handlers_disconnect_by_func (chat,
2714 chat_window_chat_notify_cb, NULL);
2716 remote_contact = g_object_get_data (G_OBJECT (chat),
2717 "chat-window-remote-contact");
2721 g_signal_handlers_disconnect_by_func (remote_contact,
2722 chat_window_update_chat_tab, chat);
2725 chat_manager = empathy_chat_manager_dup_singleton ();
2726 empathy_chat_manager_closed_chat (chat_manager, chat);
2727 g_object_unref (chat_manager);
2729 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2731 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2733 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2735 g_object_unref (chat);
2739 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2740 EmpathyChatWindow *new_window,
2745 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2746 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2747 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2749 widget = GTK_WIDGET (chat);
2751 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2752 G_OBJECT (widget)->ref_count);
2754 /* We reference here to make sure we don't loose the widget
2755 * and the EmpathyChat object during the move.
2757 g_object_ref (chat);
2758 g_object_ref (widget);
2760 empathy_chat_window_remove_chat (old_window, chat);
2761 empathy_chat_window_add_chat (new_window, chat);
2763 g_object_unref (widget);
2764 g_object_unref (chat);
2768 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2773 g_return_if_fail (self != NULL);
2774 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2776 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2779 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2784 empathy_chat_window_find_chat (TpAccount *account,
2786 gboolean sms_channel)
2790 g_return_val_if_fail (!TPAW_STR_EMPTY (id), NULL);
2792 for (l = chat_windows; l; l = l->next)
2794 EmpathyChatWindow *window = l->data;
2797 for (ll = window->priv->chats; ll; ll = ll->next)
2803 if (account == empathy_chat_get_account (chat) &&
2804 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2805 sms_channel == empathy_chat_is_sms_channel (chat))
2814 empathy_chat_window_present_chat (EmpathyChat *chat,
2817 EmpathyChatWindow *self;
2818 guint32 x_timestamp;
2820 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2822 self = chat_window_find_chat (chat);
2824 /* If the chat has no window, create one */
2827 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2830 self = empathy_chat_window_new ();
2832 /* we want to display the newly created window even if we
2833 * don't present it */
2834 gtk_widget_show (GTK_WIDGET (self));
2837 empathy_chat_window_add_chat (self, chat);
2840 /* Don't force the window to show itself when it wasn't
2841 * an action by the user
2843 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2846 if (x_timestamp != GDK_CURRENT_TIME)
2848 /* Don't present or switch tab if the action was earlier than the
2849 * last actions X time, accounting for overflow and the first ever
2852 if (self->priv->x_user_action_time != 0
2853 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2856 self->priv->x_user_action_time = x_timestamp;
2859 empathy_chat_window_switch_to_chat (self, chat);
2861 /* Don't use tpaw_window_present_with_time () which would move the window
2862 * to our current desktop but move to the window's desktop instead. This is
2863 * more coherent with Shell's 'app is ready' notication which moves the view
2864 * to the app desktop rather than moving the app itself. */
2865 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2867 gtk_widget_grab_focus (chat->input_text_view);
2872 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2877 guint _nb_rooms = 0, _nb_private = 0;
2879 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2881 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2887 if (nb_rooms != NULL)
2888 *nb_rooms = _nb_rooms;
2889 if (nb_private != NULL)
2890 *nb_private = _nb_private;
2893 EmpathyIndividualManager *
2894 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2896 return self->priv->individual_mgr;