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-2008 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>
35 #include <glib/gi18n.h>
36 #include <libnotify/notification.h>
38 #include <telepathy-glib/telepathy-glib.h>
40 #include <libempathy/empathy-contact.h>
41 #include <libempathy/empathy-message.h>
42 #include <libempathy/empathy-chatroom-manager.h>
43 #include <libempathy/empathy-utils.h>
44 #include <libempathy/empathy-tp-contact-factory.h>
45 #include <libempathy/empathy-contact-list.h>
47 #include <libempathy-gtk/empathy-images.h>
48 #include <libempathy-gtk/empathy-conf.h>
49 #include <libempathy-gtk/empathy-contact-dialogs.h>
50 #include <libempathy-gtk/empathy-log-window.h>
51 #include <libempathy-gtk/empathy-geometry.h>
52 #include <libempathy-gtk/empathy-smiley-manager.h>
53 #include <libempathy-gtk/empathy-sound.h>
54 #include <libempathy-gtk/empathy-ui-utils.h>
55 #include <libempathy-gtk/empathy-notify-manager.h>
57 #include "empathy-chat-manager.h"
58 #include "empathy-chat-window.h"
59 #include "empathy-about-dialog.h"
60 #include "empathy-invite-participant-dialog.h"
62 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
63 #include <libempathy/empathy-debug.h>
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
67 EmpathyChat *current_chat;
70 GList *chats_composing;
72 gboolean dnd_same_window;
73 EmpathyChatroomManager *chatroom_manager;
74 EmpathyNotifyManager *notify_mgr;
77 NotifyNotification *notification;
79 GtkTargetList *contact_targets;
80 GtkTargetList *file_targets;
82 EmpathyChatManager *chat_manager;
83 gulong chat_manager_chats_changed_id;
86 GtkUIManager *ui_manager;
87 GtkAction *menu_conv_insert_smiley;
88 GtkAction *menu_conv_favorite;
89 GtkAction *menu_conv_toggle_contacts;
91 GtkAction *menu_edit_cut;
92 GtkAction *menu_edit_copy;
93 GtkAction *menu_edit_paste;
94 GtkAction *menu_edit_find;
96 GtkAction *menu_tabs_next;
97 GtkAction *menu_tabs_prev;
98 GtkAction *menu_tabs_undo_close_tab;
99 GtkAction *menu_tabs_left;
100 GtkAction *menu_tabs_right;
101 GtkAction *menu_tabs_detach;
102 } EmpathyChatWindowPriv;
104 static GList *chat_windows = NULL;
106 static const guint tab_accel_keys[] = {
107 GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
108 GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
112 DND_DRAG_TYPE_CONTACT_ID,
113 DND_DRAG_TYPE_URI_LIST,
117 static const GtkTargetEntry drag_types_dest[] = {
118 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
119 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
120 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
121 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
124 static const GtkTargetEntry drag_types_dest_contact[] = {
125 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
128 static const GtkTargetEntry drag_types_dest_file[] = {
129 /* must be first to be prioritized, in order to receive the
130 * note's file path from Tomboy instead of an URI */
131 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
132 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
135 static void chat_window_update (EmpathyChatWindow *window);
137 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
140 chat_window_accel_cb (GtkAccelGroup *accelgroup,
144 EmpathyChatWindow *window)
146 EmpathyChatWindowPriv *priv;
150 priv = GET_PRIV (window);
152 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
153 if (tab_accel_keys[i] == key) {
160 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
164 static EmpathyChatWindow *
165 chat_window_find_chat (EmpathyChat *chat)
167 EmpathyChatWindowPriv *priv;
170 for (l = chat_windows; l; l = l->next) {
171 priv = GET_PRIV (l->data);
172 ll = g_list_find (priv->chats, chat);
182 chat_window_close_clicked_cb (GtkAction *action,
185 EmpathyChatWindow *window;
187 window = chat_window_find_chat (chat);
188 empathy_chat_window_remove_chat (window, chat);
192 chat_tab_style_set_cb (GtkWidget *hbox,
193 GtkStyle *previous_style,
197 int char_width, h, w;
198 PangoContext *context;
199 PangoFontMetrics *metrics;
201 button = g_object_get_data (G_OBJECT (user_data),
202 "chat-window-tab-close-button");
203 context = gtk_widget_get_pango_context (hbox);
205 metrics = pango_context_get_metrics (context, gtk_widget_get_style (hbox)->font_desc,
206 pango_context_get_language (context));
207 char_width = pango_font_metrics_get_approximate_char_width (metrics);
208 pango_font_metrics_unref (metrics);
210 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
211 GTK_ICON_SIZE_MENU, &w, &h);
213 /* Request at least about 12 chars width plus at least space for the status
214 * image and the close button */
215 gtk_widget_set_size_request (hbox,
216 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
218 gtk_widget_set_size_request (button, w, h);
222 chat_window_create_label (EmpathyChatWindow *window,
224 gboolean is_tab_label)
226 EmpathyChatWindowPriv *priv;
228 GtkWidget *name_label;
229 GtkWidget *status_image;
230 GtkWidget *close_button;
231 GtkWidget *close_image;
232 GtkWidget *event_box;
233 GtkWidget *event_box_hbox;
234 PangoAttrList *attr_list;
235 PangoAttribute *attr;
237 priv = GET_PRIV (window);
239 /* The spacing between the button and the label. */
240 hbox = gtk_hbox_new (FALSE, 0);
242 event_box = gtk_event_box_new ();
243 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
245 name_label = gtk_label_new (NULL);
247 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
249 attr_list = pango_attr_list_new ();
250 attr = pango_attr_scale_new (1/1.2);
251 attr->start_index = 0;
252 attr->end_index = -1;
253 pango_attr_list_insert (attr_list, attr);
254 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
255 pango_attr_list_unref (attr_list);
257 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
258 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
259 g_object_set_data (G_OBJECT (chat),
260 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
263 status_image = gtk_image_new ();
265 /* Spacing between the icon and label. */
266 event_box_hbox = gtk_hbox_new (FALSE, 0);
268 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
269 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
271 g_object_set_data (G_OBJECT (chat),
272 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
274 g_object_set_data (G_OBJECT (chat),
275 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
278 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
279 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
282 close_button = gtk_button_new ();
283 gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
284 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
286 /* We don't want focus/keynav for the button to avoid clutter, and
287 * Ctrl-W works anyway.
289 gtk_widget_set_can_focus (close_button, FALSE);
290 gtk_widget_set_can_default (close_button, FALSE);
292 /* Set the name to make the special rc style match. */
293 gtk_widget_set_name (close_button, "empathy-close-button");
295 close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
297 gtk_container_add (GTK_CONTAINER (close_button), close_image);
299 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
301 g_signal_connect (close_button,
303 G_CALLBACK (chat_window_close_clicked_cb),
306 /* React to theme changes and also setup the size correctly. */
307 g_signal_connect (hbox,
309 G_CALLBACK (chat_tab_style_set_cb),
313 gtk_widget_show_all (hbox);
319 _submenu_notify_visible_changed_cb (GObject *object,
323 g_signal_handlers_disconnect_by_func (object,
324 _submenu_notify_visible_changed_cb,
326 chat_window_update (EMPATHY_CHAT_WINDOW (userdata));
330 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
335 gboolean wrap_around;
336 gboolean is_connected;
339 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
340 first_page = (page_num == 0);
341 last_page = (page_num == (num_pages - 1));
342 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
344 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
346 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
348 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
350 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
351 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
352 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
353 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
357 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
358 EmpathyChatWindow *self)
360 EmpathyTpChat *tp_chat;
361 TpConnection *connection;
363 gboolean sensitive = FALSE;
365 g_return_if_fail (priv->current_chat != NULL);
367 action = gtk_ui_manager_get_action (priv->ui_manager,
368 "/chats_menubar/menu_conv/menu_conv_invite_participant");
369 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
371 if (tp_chat != NULL) {
372 connection = empathy_tp_chat_get_connection (tp_chat);
374 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
375 (tp_connection_get_status (connection, NULL) ==
376 TP_CONNECTION_STATUS_CONNECTED);
379 gtk_action_set_sensitive (action, sensitive);
383 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
384 EmpathyChatWindow *window)
386 GtkWidget *menu, *submenu, *orig_submenu;
388 menu = gtk_ui_manager_get_widget (priv->ui_manager,
389 "/chats_menubar/menu_contact");
390 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
392 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
393 submenu = empathy_chat_get_contact_menu (priv->current_chat);
394 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
395 gtk_widget_show (menu);
397 tp_g_signal_connect_object (orig_submenu,
399 (GCallback)_submenu_notify_visible_changed_cb,
405 get_all_unread_messages (EmpathyChatWindowPriv *priv)
410 for (l = priv->chats_new_msg; l != NULL; l = g_list_next (l)) {
411 EmpathyChat *chat = l->data;
413 nb += empathy_chat_get_nb_unread_messages (chat);
420 get_window_title_name (EmpathyChatWindowPriv *priv)
422 const gchar *active_name;
424 guint current_unread_msgs;
426 nb_chats = g_list_length (priv->chats);
427 g_assert (nb_chats > 0);
429 active_name = empathy_chat_get_name (priv->current_chat);
431 current_unread_msgs = empathy_chat_get_nb_unread_messages (
436 if (current_unread_msgs == 0)
437 return g_strdup (active_name);
439 return g_strdup_printf (ngettext (
441 "%s (%d unread)", current_unread_msgs),
442 active_name, current_unread_msgs);
444 guint nb_others = nb_chats - 1;
445 guint all_unread_msgs;
447 all_unread_msgs = get_all_unread_messages (priv);
449 if (all_unread_msgs == 0) {
450 /* no unread message */
451 return g_strdup_printf (ngettext (
453 "%s (and %u others)", nb_others),
454 active_name, nb_others);
457 else if (all_unread_msgs == current_unread_msgs) {
458 /* unread messages are in the current tab */
459 return g_strdup_printf (ngettext (
461 "%s (%d unread)", current_unread_msgs),
462 active_name, current_unread_msgs);
465 else if (current_unread_msgs == 0) {
466 /* unread messages are in other tabs */
467 return g_strdup_printf (ngettext (
468 "%s (%d unread from others)",
469 "%s (%d unread from others)",
471 active_name, all_unread_msgs);
475 /* unread messages are in all the tabs */
476 return g_strdup_printf (ngettext (
477 "%s (%d unread from all)",
478 "%s (%d unread from all)",
480 active_name, all_unread_msgs);
486 chat_window_title_update (EmpathyChatWindowPriv *priv)
490 name = get_window_title_name (priv);
491 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
496 chat_window_icon_update (EmpathyChatWindowPriv *priv)
499 EmpathyContact *remote_contact;
500 gboolean avatar_in_icon;
503 n_chats = g_list_length (priv->chats);
505 /* Update window icon */
506 if (priv->chats_new_msg) {
507 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
508 EMPATHY_IMAGE_MESSAGE);
510 empathy_conf_get_bool (empathy_conf_get (),
511 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON,
514 if (n_chats == 1 && avatar_in_icon) {
515 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
516 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
517 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
520 g_object_unref (icon);
523 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
529 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
533 GtkWidget *chat_close_button;
536 if (num_pages == 1) {
537 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
538 chat_close_button = g_object_get_data (G_OBJECT (chat),
539 "chat-window-tab-close-button");
540 gtk_widget_hide (chat_close_button);
542 for (i=0; i<num_pages; i++) {
543 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
544 chat_close_button = g_object_get_data (G_OBJECT (chat),
545 "chat-window-tab-close-button");
546 gtk_widget_show (chat_close_button);
552 chat_window_update (EmpathyChatWindow *window)
554 EmpathyChatWindowPriv *priv = GET_PRIV (window);
557 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
559 /* Update Tab menu */
560 chat_window_menu_context_update (priv,
563 chat_window_conversation_menu_update (priv, window);
565 chat_window_contact_menu_update (priv,
568 chat_window_title_update (priv);
570 chat_window_icon_update (priv);
572 chat_window_close_button_update (priv,
577 append_markup_printf (GString *string,
584 va_start (args, format);
586 tmp = g_markup_vprintf_escaped (format, args);
587 g_string_append (string, tmp);
594 chat_window_update_chat_tab (EmpathyChat *chat)
596 EmpathyChatWindow *window;
597 EmpathyChatWindowPriv *priv;
598 EmpathyContact *remote_contact;
602 const gchar *subject;
603 const gchar *status = NULL;
607 const gchar *icon_name;
608 GtkWidget *tab_image;
609 GtkWidget *menu_image;
611 window = chat_window_find_chat (chat);
615 priv = GET_PRIV (window);
617 /* Get information */
618 name = empathy_chat_get_name (chat);
619 account = empathy_chat_get_account (chat);
620 subject = empathy_chat_get_subject (chat);
621 remote_contact = empathy_chat_get_remote_contact (chat);
623 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
624 name, tp_proxy_get_object_path (account), subject, remote_contact);
626 /* Update tab image */
627 if (empathy_chat_get_tp_chat (chat) == NULL) {
628 /* No TpChat, we are disconnected */
631 else if (g_list_find (priv->chats_new_msg, chat)) {
632 icon_name = EMPATHY_IMAGE_MESSAGE;
634 else if (g_list_find (priv->chats_composing, chat)) {
635 icon_name = EMPATHY_IMAGE_TYPING;
637 else if (remote_contact) {
638 icon_name = empathy_icon_name_for_contact (remote_contact);
640 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
643 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
644 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
645 if (icon_name != NULL) {
646 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
647 gtk_widget_show (tab_image);
648 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
649 gtk_widget_show (menu_image);
651 gtk_widget_hide (tab_image);
652 gtk_widget_hide (menu_image);
655 /* Update tab tooltip */
656 tooltip = g_string_new (NULL);
658 if (remote_contact) {
659 id = empathy_contact_get_id (remote_contact);
660 status = empathy_contact_get_presence_message (remote_contact);
665 append_markup_printf (tooltip,
666 "<b>%s</b><small> (%s)</small>",
668 tp_account_get_display_name (account));
670 if (!EMP_STR_EMPTY (status)) {
671 append_markup_printf (tooltip, "\n<i>%s</i>", status);
675 append_markup_printf (tooltip, "\n<b>%s</b> %s",
676 _("Topic:"), subject);
679 if (g_list_find (priv->chats_composing, chat)) {
680 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
683 markup = g_string_free (tooltip, FALSE);
684 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
685 gtk_widget_set_tooltip_markup (widget, markup);
686 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
687 gtk_widget_set_tooltip_markup (widget, markup);
690 /* Update tab and menu label */
691 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
692 gtk_label_set_text (GTK_LABEL (widget), name);
693 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
694 gtk_label_set_text (GTK_LABEL (widget), name);
696 /* Update the window if it's the current chat */
697 if (priv->current_chat == chat) {
698 chat_window_update (window);
703 chat_window_chat_notify_cb (EmpathyChat *chat)
705 EmpathyContact *old_remote_contact;
706 EmpathyContact *remote_contact = NULL;
708 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
709 remote_contact = empathy_chat_get_remote_contact (chat);
711 if (old_remote_contact != remote_contact) {
712 /* The remote-contact associated with the chat changed, we need
713 * to keep track of any change of that contact and update the
714 * window each time. */
715 if (remote_contact) {
716 g_signal_connect_swapped (remote_contact, "notify",
717 G_CALLBACK (chat_window_update_chat_tab),
720 if (old_remote_contact) {
721 g_signal_handlers_disconnect_by_func (old_remote_contact,
722 chat_window_update_chat_tab,
726 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
727 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
730 chat_window_update_chat_tab (chat);
734 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
735 EmpathySmiley *smiley,
738 EmpathyChatWindowPriv *priv = GET_PRIV (window);
740 GtkTextBuffer *buffer;
743 chat = priv->current_chat;
745 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
746 gtk_text_buffer_get_end_iter (buffer, &iter);
747 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
751 chat_window_conv_activate_cb (GtkAction *action,
752 EmpathyChatWindow *window)
754 EmpathyChatWindowPriv *priv = GET_PRIV (window);
757 EmpathyContact *remote_contact = NULL;
759 /* Favorite room menu */
760 is_room = empathy_chat_is_room (priv->current_chat);
764 gboolean found = FALSE;
765 EmpathyChatroom *chatroom;
767 room = empathy_chat_get_id (priv->current_chat);
768 account = empathy_chat_get_account (priv->current_chat);
769 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
771 if (chatroom != NULL)
772 found = empathy_chatroom_is_favorite (chatroom);
774 DEBUG ("This room %s favorite", found ? "is" : "is not");
775 gtk_toggle_action_set_active (
776 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
778 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
780 /* Show contacts menu */
781 g_object_get (priv->current_chat,
782 "remote-contact", &remote_contact,
783 "show-contacts", &active,
785 if (remote_contact == NULL) {
786 gtk_toggle_action_set_active (
787 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
790 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
791 (remote_contact == NULL));
792 if (remote_contact != NULL) {
793 g_object_unref (remote_contact);
798 chat_window_clear_activate_cb (GtkAction *action,
799 EmpathyChatWindow *window)
801 EmpathyChatWindowPriv *priv = GET_PRIV (window);
803 empathy_chat_clear (priv->current_chat);
807 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
808 EmpathyChatWindow *window)
810 EmpathyChatWindowPriv *priv = GET_PRIV (window);
814 EmpathyChatroom *chatroom;
816 active = gtk_toggle_action_get_active (toggle_action);
817 account = empathy_chat_get_account (priv->current_chat);
818 room = empathy_chat_get_id (priv->current_chat);
820 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
823 if (chatroom == NULL) {
826 name = empathy_chat_get_name (priv->current_chat);
827 chatroom = empathy_chatroom_new_full (account, room, name, FALSE);
828 empathy_chatroom_manager_add (priv->chatroom_manager, chatroom);
829 g_object_unref (chatroom);
832 empathy_chatroom_set_favorite (chatroom, active);
836 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
837 EmpathyChatWindow *window)
839 EmpathyChatWindowPriv *priv = GET_PRIV (window);
842 active = gtk_toggle_action_get_active (toggle_action);
844 empathy_chat_set_show_contacts (priv->current_chat, active);
848 got_contact_cb (EmpathyTpContactFactory *factory,
849 EmpathyContact *contact,
854 EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data);
857 DEBUG ("Failed: %s", error->message);
860 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
861 contact, _("Inviting you to this room"));
866 chat_window_invite_participant_activate_cb (GtkAction *action,
867 EmpathyChatWindow *window)
869 EmpathyChatWindowPriv *priv;
871 EmpathyTpChat *tp_chat;
876 priv = GET_PRIV (window);
878 g_return_if_fail (priv->current_chat != NULL);
880 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
881 channel = empathy_tp_chat_get_channel (tp_chat);
882 account = empathy_chat_get_account (priv->current_chat);
884 dialog = empathy_invite_participant_dialog_new (
885 GTK_WINDOW (priv->dialog), account);
886 gtk_widget_show (dialog);
888 response = gtk_dialog_run (GTK_DIALOG (dialog));
890 if (response == GTK_RESPONSE_ACCEPT) {
891 TpConnection *connection;
892 EmpathyTpContactFactory *factory;
895 id = empathy_contact_selector_dialog_get_selected (
896 EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL);
897 if (EMP_STR_EMPTY (id)) goto out;
899 connection = tp_channel_borrow_connection (channel);
900 factory = empathy_tp_contact_factory_dup_singleton (connection);
902 empathy_tp_contact_factory_get_from_id (factory, id,
903 got_contact_cb, tp_chat, NULL, NULL);
905 g_object_unref (factory);
909 gtk_widget_destroy (dialog);
913 chat_window_close_activate_cb (GtkAction *action,
914 EmpathyChatWindow *window)
916 EmpathyChatWindowPriv *priv;
918 priv = GET_PRIV (window);
920 g_return_if_fail (priv->current_chat != NULL);
922 empathy_chat_window_remove_chat (window, priv->current_chat);
926 chat_window_edit_activate_cb (GtkAction *action,
927 EmpathyChatWindow *window)
929 EmpathyChatWindowPriv *priv;
930 GtkClipboard *clipboard;
931 GtkTextBuffer *buffer;
932 gboolean text_available;
934 priv = GET_PRIV (window);
936 g_return_if_fail (priv->current_chat != NULL);
938 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
939 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
940 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
941 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
945 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
946 if (gtk_text_buffer_get_has_selection (buffer)) {
947 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
948 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
952 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
954 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
955 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
958 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
959 text_available = gtk_clipboard_wait_is_text_available (clipboard);
960 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
964 chat_window_cut_activate_cb (GtkAction *action,
965 EmpathyChatWindow *window)
967 EmpathyChatWindowPriv *priv;
969 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
971 priv = GET_PRIV (window);
973 empathy_chat_cut (priv->current_chat);
977 chat_window_copy_activate_cb (GtkAction *action,
978 EmpathyChatWindow *window)
980 EmpathyChatWindowPriv *priv;
982 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
984 priv = GET_PRIV (window);
986 empathy_chat_copy (priv->current_chat);
990 chat_window_paste_activate_cb (GtkAction *action,
991 EmpathyChatWindow *window)
993 EmpathyChatWindowPriv *priv;
995 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
997 priv = GET_PRIV (window);
999 empathy_chat_paste (priv->current_chat);
1003 chat_window_find_activate_cb (GtkAction *action,
1004 EmpathyChatWindow *window)
1006 EmpathyChatWindowPriv *priv;
1008 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1010 priv = GET_PRIV (window);
1012 empathy_chat_find (priv->current_chat);
1016 chat_window_tabs_next_activate_cb (GtkAction *action,
1017 EmpathyChatWindow *window)
1019 EmpathyChatWindowPriv *priv;
1021 gint index_, numPages;
1022 gboolean wrap_around;
1024 priv = GET_PRIV (window);
1026 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1027 &wrap_around, NULL);
1029 chat = priv->current_chat;
1030 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1031 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1033 if (index_ == (numPages - 1) && wrap_around) {
1034 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1038 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1042 chat_window_tabs_previous_activate_cb (GtkAction *action,
1043 EmpathyChatWindow *window)
1045 EmpathyChatWindowPriv *priv;
1047 gint index_, numPages;
1048 gboolean wrap_around;
1050 priv = GET_PRIV (window);
1052 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1053 &wrap_around, NULL);
1055 chat = priv->current_chat;
1056 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1057 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1059 if (index_ <= 0 && wrap_around) {
1060 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1064 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1068 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1069 EmpathyChatWindow *window)
1071 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1072 empathy_chat_manager_undo_closed_chat (priv->chat_manager);
1076 chat_window_tabs_left_activate_cb (GtkAction *action,
1077 EmpathyChatWindow *window)
1079 EmpathyChatWindowPriv *priv;
1081 gint index_, num_pages;
1083 priv = GET_PRIV (window);
1085 chat = priv->current_chat;
1086 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1091 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1095 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1096 chat_window_menu_context_update (priv, num_pages);
1100 chat_window_tabs_right_activate_cb (GtkAction *action,
1101 EmpathyChatWindow *window)
1103 EmpathyChatWindowPriv *priv;
1105 gint index_, num_pages;
1107 priv = GET_PRIV (window);
1109 chat = priv->current_chat;
1110 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1112 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1116 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1117 chat_window_menu_context_update (priv, num_pages);
1121 chat_window_detach_activate_cb (GtkAction *action,
1122 EmpathyChatWindow *window)
1124 EmpathyChatWindowPriv *priv;
1125 EmpathyChatWindow *new_window;
1128 priv = GET_PRIV (window);
1130 chat = priv->current_chat;
1131 new_window = empathy_chat_window_new ();
1133 empathy_chat_window_move_chat (window, new_window, chat);
1135 priv = GET_PRIV (new_window);
1136 gtk_widget_show (priv->dialog);
1140 chat_window_help_contents_activate_cb (GtkAction *action,
1141 EmpathyChatWindow *window)
1143 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1145 empathy_url_show (priv->dialog, "ghelp:empathy");
1149 chat_window_help_about_activate_cb (GtkAction *action,
1150 EmpathyChatWindow *window)
1152 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1154 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1158 chat_window_delete_event_cb (GtkWidget *dialog,
1160 EmpathyChatWindow *window)
1162 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1164 DEBUG ("Delete event received");
1166 g_object_ref (window);
1167 while (priv->chats) {
1168 empathy_chat_window_remove_chat (window, priv->chats->data);
1170 g_object_unref (window);
1176 chat_window_composing_cb (EmpathyChat *chat,
1177 gboolean is_composing,
1178 EmpathyChatWindow *window)
1180 EmpathyChatWindowPriv *priv;
1182 priv = GET_PRIV (window);
1184 if (is_composing && !g_list_find (priv->chats_composing, chat)) {
1185 priv->chats_composing = g_list_prepend (priv->chats_composing, chat);
1187 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1190 chat_window_update_chat_tab (chat);
1194 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1197 EmpathyChatWindowPriv *priv;
1199 priv = GET_PRIV (window);
1201 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1205 chat_window_notification_closed_cb (NotifyNotification *notify,
1206 EmpathyChatWindow *self)
1208 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1210 g_object_unref (notify);
1211 if (priv->notification == notify) {
1212 priv->notification = NULL;
1217 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1218 EmpathyMessage *message,
1221 EmpathyContact *sender;
1222 const gchar *header;
1226 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1227 gboolean res, has_x_canonical_append;
1228 NotifyNotification *notification = priv->notification;
1230 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1233 empathy_conf_get_bool (empathy_conf_get (),
1234 EMPATHY_PREFS_NOTIFICATIONS_FOCUS, &res);
1240 sender = empathy_message_get_sender (message);
1241 header = empathy_contact_get_name (sender);
1242 body = empathy_message_get_body (message);
1243 escaped = g_markup_escape_text (body, -1);
1244 has_x_canonical_append = empathy_notify_manager_has_capability (
1245 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1247 if (notification != NULL && !has_x_canonical_append) {
1248 /* if the notification server supports x-canonical-append, it is
1249 better to not use notify_notification_update to avoid
1250 overwriting the current notification message */
1251 notify_notification_update (notification,
1252 header, escaped, NULL);
1254 /* if the notification server supports x-canonical-append,
1255 the hint will be added, so that the message from the
1256 just created notification will be automatically appended
1257 to an existing notification with the same title.
1258 In this way the previous message will not be lost: the new
1259 message will appear below it, in the same notification */
1260 notification = notify_notification_new (header, escaped, NULL, NULL);
1262 if (priv->notification == NULL) {
1263 priv->notification = notification;
1266 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1268 g_signal_connect (notification, "closed",
1269 G_CALLBACK (chat_window_notification_closed_cb), window);
1271 if (has_x_canonical_append) {
1272 notify_notification_set_hint_string (notification,
1273 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
1277 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1278 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1280 if (pixbuf != NULL) {
1281 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1282 g_object_unref (pixbuf);
1285 notify_notification_show (notification, NULL);
1291 chat_window_set_highlight_room_tab_label (EmpathyChat *chat)
1296 if (!empathy_chat_is_room (chat))
1299 markup = g_markup_printf_escaped (
1300 "<span color=\"red\" weight=\"bold\">%s</span>",
1301 empathy_chat_get_name (chat));
1303 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1304 gtk_label_set_markup (GTK_LABEL (widget), markup);
1309 chat_window_new_message_cb (EmpathyChat *chat,
1310 EmpathyMessage *message,
1311 EmpathyChatWindow *window)
1313 EmpathyChatWindowPriv *priv;
1315 gboolean needs_urgency;
1316 EmpathyContact *sender;
1318 priv = GET_PRIV (window);
1320 has_focus = empathy_chat_window_has_focus (window);
1322 /* - if we're the sender, we play the sound if it's specified in the
1323 * preferences and we're not away.
1324 * - if we receive a message, we play the sound if it's specified in the
1325 * preferences and the window does not have focus on the chat receiving
1329 sender = empathy_message_get_sender (message);
1331 if (empathy_contact_is_user (sender)) {
1332 empathy_sound_play (GTK_WIDGET (priv->dialog),
1333 EMPATHY_SOUND_MESSAGE_OUTGOING);
1336 if (has_focus && priv->current_chat == chat) {
1337 /* window and tab are focused so consider the message to be read */
1339 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1340 empathy_chat_messages_read (chat);
1344 if (!g_list_find (priv->chats_new_msg, chat)) {
1345 priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
1346 chat_window_update_chat_tab (chat);
1349 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1350 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1351 * an unamed MUC (msn-like).
1352 * In case of a MUC, we set urgency only if the message contains our
1354 if (empathy_chat_is_room (chat) ||
1355 empathy_chat_get_remote_contact (chat) == NULL) {
1356 needs_urgency = empathy_message_should_highlight (message);
1358 needs_urgency = TRUE;
1361 if (needs_urgency) {
1363 chat_window_set_urgency_hint (window, TRUE);
1364 chat_window_set_highlight_room_tab_label (chat);
1367 empathy_sound_play (GTK_WIDGET (priv->dialog),
1368 EMPATHY_SOUND_MESSAGE_INCOMING);
1369 chat_window_show_or_update_notification (window, message, chat);
1372 /* update the number of unread messages */
1373 chat_window_title_update (priv);
1376 static GtkNotebook *
1377 chat_window_detach_hook (GtkNotebook *source,
1383 EmpathyChatWindowPriv *priv;
1384 EmpathyChatWindow *window, *new_window;
1387 chat = EMPATHY_CHAT (page);
1388 window = chat_window_find_chat (chat);
1390 new_window = empathy_chat_window_new ();
1391 priv = GET_PRIV (new_window);
1393 DEBUG ("Detach hook called");
1395 empathy_chat_window_move_chat (window, new_window, chat);
1397 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1398 gtk_widget_show (priv->dialog);
1404 chat_window_page_switched_cb (GtkNotebook *notebook,
1405 GtkNotebookPage *page,
1407 EmpathyChatWindow *window)
1409 EmpathyChatWindowPriv *priv;
1413 DEBUG ("Page switched");
1415 priv = GET_PRIV (window);
1417 child = gtk_notebook_get_nth_page (notebook, page_num);
1418 chat = EMPATHY_CHAT (child);
1420 if (priv->page_added) {
1421 priv->page_added = FALSE;
1422 empathy_chat_scroll_down (chat);
1424 else if (priv->current_chat == chat) {
1428 priv->current_chat = chat;
1429 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1430 empathy_chat_messages_read (chat);
1432 chat_window_update_chat_tab (chat);
1436 chat_window_page_added_cb (GtkNotebook *notebook,
1439 EmpathyChatWindow *window)
1441 EmpathyChatWindowPriv *priv;
1444 priv = GET_PRIV (window);
1446 /* If we just received DND to the same window, we don't want
1447 * to do anything here like removing the tab and then readding
1448 * it, so we return here and in "page-added".
1450 if (priv->dnd_same_window) {
1451 DEBUG ("Page added (back to the same window)");
1452 priv->dnd_same_window = FALSE;
1456 DEBUG ("Page added");
1458 /* Get chat object */
1459 chat = EMPATHY_CHAT (child);
1461 /* Connect chat signals for this window */
1462 g_signal_connect (chat, "composing",
1463 G_CALLBACK (chat_window_composing_cb),
1465 g_signal_connect (chat, "new-message",
1466 G_CALLBACK (chat_window_new_message_cb),
1468 g_signal_connect (chat, "notify::tp-chat",
1469 G_CALLBACK (chat_window_update_chat_tab),
1472 /* Set flag so we know to perform some special operations on
1473 * switch page due to the new page being added.
1475 priv->page_added = TRUE;
1477 /* Get list of chats up to date */
1478 priv->chats = g_list_append (priv->chats, chat);
1480 chat_window_update_chat_tab (chat);
1484 chat_window_page_removed_cb (GtkNotebook *notebook,
1487 EmpathyChatWindow *window)
1489 EmpathyChatWindowPriv *priv;
1492 priv = GET_PRIV (window);
1494 /* If we just received DND to the same window, we don't want
1495 * to do anything here like removing the tab and then readding
1496 * it, so we return here and in "page-added".
1498 if (priv->dnd_same_window) {
1499 DEBUG ("Page removed (and will be readded to same window)");
1503 DEBUG ("Page removed");
1505 /* Get chat object */
1506 chat = EMPATHY_CHAT (child);
1508 /* Disconnect all signal handlers for this chat and this window */
1509 g_signal_handlers_disconnect_by_func (chat,
1510 G_CALLBACK (chat_window_composing_cb),
1512 g_signal_handlers_disconnect_by_func (chat,
1513 G_CALLBACK (chat_window_new_message_cb),
1515 g_signal_handlers_disconnect_by_func (chat,
1516 G_CALLBACK (chat_window_update_chat_tab),
1519 /* Keep list of chats up to date */
1520 priv->chats = g_list_remove (priv->chats, chat);
1521 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1522 empathy_chat_messages_read (chat);
1523 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1525 if (priv->chats == NULL) {
1526 g_object_unref (window);
1528 chat_window_update (window);
1533 chat_window_focus_in_event_cb (GtkWidget *widget,
1535 EmpathyChatWindow *window)
1537 EmpathyChatWindowPriv *priv;
1539 priv = GET_PRIV (window);
1541 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
1542 empathy_chat_messages_read (priv->current_chat);
1544 chat_window_set_urgency_hint (window, FALSE);
1546 /* Update the title, since we now mark all unread messages as read. */
1547 chat_window_update_chat_tab (priv->current_chat);
1553 chat_window_drag_drop (GtkWidget *widget,
1554 GdkDragContext *context,
1558 EmpathyChatWindow *window)
1561 EmpathyChatWindowPriv *priv;
1563 priv = GET_PRIV (window);
1565 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1566 if (target == GDK_NONE)
1567 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1569 if (target != GDK_NONE) {
1570 gtk_drag_get_data (widget, context, target, time_);
1578 chat_window_drag_motion (GtkWidget *widget,
1579 GdkDragContext *context,
1583 EmpathyChatWindow *window)
1586 EmpathyChatWindowPriv *priv;
1588 priv = GET_PRIV (window);
1590 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1591 if (target != GDK_NONE) {
1592 /* This is a file drag. Ensure the contact is online and set the
1593 drag type to COPY. Note that it's possible that the tab will
1594 be switched by GTK+ after a timeout from drag_motion without
1595 getting another drag_motion to disable the drop. You have
1596 to hold your mouse really still.
1598 EmpathyContact *contact;
1600 priv = GET_PRIV (window);
1601 contact = empathy_chat_get_remote_contact (priv->current_chat);
1602 /* contact is NULL for multi-user chats. We don't do
1603 * file transfers to MUCs. We also don't send files
1604 * to offline contacts or contacts that don't support
1607 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1608 gdk_drag_status (context, 0, time_);
1611 if (!(empathy_contact_get_capabilities (contact)
1612 & EMPATHY_CAPABILITIES_FT)) {
1613 gdk_drag_status (context, 0, time_);
1616 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1620 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1621 if (target != GDK_NONE) {
1622 /* This is a drag of a contact from a contact list. Set to COPY.
1623 FIXME: If this drag is to a MUC window, it invites the user.
1624 Otherwise, it opens a chat. Should we use a different drag
1625 type for invites? Should we allow ASK?
1627 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1635 chat_window_drag_data_received (GtkWidget *widget,
1636 GdkDragContext *context,
1639 GtkSelectionData *selection,
1642 EmpathyChatWindow *window)
1644 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1645 EmpathyChat *chat = NULL;
1646 EmpathyChatWindow *old_window;
1647 TpAccount *account = NULL;
1648 TpAccountManager *account_manager;
1651 const gchar *account_id;
1652 const gchar *contact_id;
1654 id = (const gchar*) gtk_selection_data_get_data (selection);
1656 /* FIXME: Perhaps should be sure that the account manager is
1657 * prepared before calling _ensure_account on it. */
1658 account_manager = tp_account_manager_dup ();
1660 DEBUG ("DND contact from roster with id:'%s'", id);
1662 strv = g_strsplit (id, ":", 2);
1663 if (g_strv_length (strv) == 2) {
1664 account_id = strv[0];
1665 contact_id = strv[1];
1667 tp_account_manager_ensure_account (account_manager, account_id);
1668 if (account != NULL)
1669 chat = empathy_chat_window_find_chat (account, contact_id);
1672 if (account == NULL) {
1674 gtk_drag_finish (context, FALSE, FALSE, time_);
1679 TpConnection *connection;
1681 connection = tp_account_get_connection (account);
1684 empathy_dispatcher_chat_with_contact_id (
1685 connection, contact_id, gtk_get_current_event_time (), NULL, NULL);
1691 g_object_unref (account_manager);
1694 old_window = chat_window_find_chat (chat);
1696 if (old_window == window) {
1697 gtk_drag_finish (context, TRUE, FALSE, time_);
1701 empathy_chat_window_move_chat (old_window, window, chat);
1703 empathy_chat_window_add_chat (window, chat);
1706 /* Added to take care of any outstanding chat events */
1707 empathy_chat_window_present_chat (chat,
1708 EMPATHY_DISPATCHER_NON_USER_ACTION);
1710 /* We should return TRUE to remove the data when doing
1711 * GDK_ACTION_MOVE, but we don't here otherwise it has
1712 * weird consequences, and we handle that internally
1713 * anyway with add_chat () and remove_chat ().
1715 gtk_drag_finish (context, TRUE, FALSE, time_);
1717 else if (info == DND_DRAG_TYPE_URI_LIST) {
1718 EmpathyChatWindowPriv *priv;
1719 EmpathyContact *contact;
1722 priv = GET_PRIV (window);
1723 contact = empathy_chat_get_remote_contact (priv->current_chat);
1725 /* contact is NULL when current_chat is a multi-user chat.
1726 * We don't do file transfers to MUCs, so just cancel the drag.
1728 if (contact == NULL) {
1729 gtk_drag_finish (context, TRUE, FALSE, time_);
1733 data = (const gchar *) gtk_selection_data_get_data (selection);
1734 empathy_send_file_from_uri_list (contact, data);
1736 gtk_drag_finish (context, TRUE, FALSE, time_);
1738 else if (info == DND_DRAG_TYPE_TAB) {
1740 EmpathyChatWindow *old_window = NULL;
1744 chat = (void *) gtk_selection_data_get_data (selection);
1745 old_window = chat_window_find_chat (*chat);
1748 EmpathyChatWindowPriv *priv;
1750 priv = GET_PRIV (window);
1751 priv->dnd_same_window = (old_window == window);
1752 DEBUG ("DND tab (within same window: %s)",
1753 priv->dnd_same_window ? "Yes" : "No");
1756 DEBUG ("DND from unknown source");
1757 gtk_drag_finish (context, FALSE, FALSE, time_);
1762 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1763 guint num_chats_in_manager,
1764 EmpathyChatWindow *window)
1766 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1768 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1769 num_chats_in_manager > 0);
1773 chat_window_finalize (GObject *object)
1775 EmpathyChatWindow *window;
1776 EmpathyChatWindowPriv *priv;
1778 window = EMPATHY_CHAT_WINDOW (object);
1779 priv = GET_PRIV (window);
1781 DEBUG ("Finalized: %p", object);
1783 g_object_unref (priv->ui_manager);
1784 g_object_unref (priv->chatroom_manager);
1785 g_object_unref (priv->notify_mgr);
1787 if (priv->notification != NULL) {
1788 notify_notification_close (priv->notification, NULL);
1789 priv->notification = NULL;
1792 if (priv->contact_targets) {
1793 gtk_target_list_unref (priv->contact_targets);
1795 if (priv->file_targets) {
1796 gtk_target_list_unref (priv->file_targets);
1799 if (priv->chat_manager) {
1800 g_signal_handler_disconnect (priv->chat_manager,
1801 priv->chat_manager_chats_changed_id);
1802 g_object_unref (priv->chat_manager);
1803 priv->chat_manager = NULL;
1806 chat_windows = g_list_remove (chat_windows, window);
1807 gtk_widget_destroy (priv->dialog);
1809 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1813 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1815 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1817 object_class->finalize = chat_window_finalize;
1819 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1821 /* Set up a style for the close button with no focus padding. */
1822 gtk_rc_parse_string (
1823 "style \"empathy-close-button-style\"\n"
1825 " GtkWidget::focus-padding = 0\n"
1829 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1831 gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1835 empathy_chat_window_init (EmpathyChatWindow *window)
1838 GtkAccelGroup *accel_group;
1843 GtkWidget *chat_vbox;
1845 EmpathySmileyManager *smiley_manager;
1846 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1847 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1849 window->priv = priv;
1850 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1851 gui = empathy_builder_get_file (filename,
1852 "chat_window", &priv->dialog,
1853 "chat_vbox", &chat_vbox,
1854 "ui_manager", &priv->ui_manager,
1855 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1856 "menu_conv_favorite", &priv->menu_conv_favorite,
1857 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1858 "menu_edit_cut", &priv->menu_edit_cut,
1859 "menu_edit_copy", &priv->menu_edit_copy,
1860 "menu_edit_paste", &priv->menu_edit_paste,
1861 "menu_edit_find", &priv->menu_edit_find,
1862 "menu_tabs_next", &priv->menu_tabs_next,
1863 "menu_tabs_prev", &priv->menu_tabs_prev,
1864 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1865 "menu_tabs_left", &priv->menu_tabs_left,
1866 "menu_tabs_right", &priv->menu_tabs_right,
1867 "menu_tabs_detach", &priv->menu_tabs_detach,
1871 empathy_builder_connect (gui, window,
1872 "menu_conv", "activate", chat_window_conv_activate_cb,
1873 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1874 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1875 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1876 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1877 "menu_conv_close", "activate", chat_window_close_activate_cb,
1878 "menu_edit", "activate", chat_window_edit_activate_cb,
1879 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1880 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1881 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1882 "menu_edit_find", "activate", chat_window_find_activate_cb,
1883 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1884 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1885 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1886 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1887 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1888 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1889 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1890 "menu_help_about", "activate", chat_window_help_about_activate_cb,
1893 g_object_ref (priv->ui_manager);
1894 g_object_unref (gui);
1896 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1898 priv->notebook = gtk_notebook_new ();
1899 gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
1900 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
1901 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
1902 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1903 gtk_widget_show (priv->notebook);
1906 accel_group = gtk_accel_group_new ();
1907 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
1909 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
1910 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
1913 gtk_accel_group_connect (accel_group,
1920 g_object_unref (accel_group);
1922 /* Set up drag target lists */
1923 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
1924 G_N_ELEMENTS (drag_types_dest_contact));
1925 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1926 G_N_ELEMENTS (drag_types_dest_file));
1928 /* Set up smiley menu */
1929 smiley_manager = empathy_smiley_manager_dup_singleton ();
1930 submenu = empathy_smiley_menu_new (smiley_manager,
1931 chat_window_insert_smiley_activate_cb,
1933 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1934 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
1935 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
1936 g_object_unref (smiley_manager);
1938 /* Set up signals we can't do with ui file since we may need to
1939 * block/unblock them at some later stage.
1942 g_signal_connect (priv->dialog,
1944 G_CALLBACK (chat_window_delete_event_cb),
1946 g_signal_connect (priv->dialog,
1948 G_CALLBACK (chat_window_focus_in_event_cb),
1950 g_signal_connect_after (priv->notebook,
1952 G_CALLBACK (chat_window_page_switched_cb),
1954 g_signal_connect (priv->notebook,
1956 G_CALLBACK (chat_window_page_added_cb),
1958 g_signal_connect (priv->notebook,
1960 G_CALLBACK (chat_window_page_removed_cb),
1963 /* Set up drag and drop */
1964 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
1965 GTK_DEST_DEFAULT_HIGHLIGHT,
1967 G_N_ELEMENTS (drag_types_dest),
1968 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1970 /* connect_after to allow GtkNotebook's built-in tab switching */
1971 g_signal_connect_after (priv->notebook,
1973 G_CALLBACK (chat_window_drag_motion),
1975 g_signal_connect (priv->notebook,
1976 "drag-data-received",
1977 G_CALLBACK (chat_window_drag_data_received),
1979 g_signal_connect (priv->notebook,
1981 G_CALLBACK (chat_window_drag_drop),
1984 chat_windows = g_list_prepend (chat_windows, window);
1986 /* Set up private details */
1988 priv->chats_new_msg = NULL;
1989 priv->chats_composing = NULL;
1990 priv->current_chat = NULL;
1991 priv->notification = NULL;
1993 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
1995 priv->chat_manager = empathy_chat_manager_dup_singleton ();
1996 priv->chat_manager_chats_changed_id =
1997 g_signal_connect (priv->chat_manager, "chats-changed",
1998 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2001 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2002 empathy_chat_manager_get_num_chats (priv->chat_manager),
2007 empathy_chat_window_new (void)
2009 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
2012 /* Returns the window to open a new tab in if there is only one window
2013 * visble, otherwise, returns NULL indicating that a new window should
2017 empathy_chat_window_get_default (gboolean room)
2020 gboolean separate_windows = TRUE;
2022 empathy_conf_get_bool (empathy_conf_get (),
2023 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
2026 if (separate_windows) {
2027 /* Always create a new window */
2031 for (l = chat_windows; l; l = l->next) {
2032 EmpathyChatWindowPriv *priv;
2033 EmpathyChatWindow *chat_window;
2036 chat_window = l->data;
2037 priv = GET_PRIV (chat_window);
2039 dialog = empathy_chat_window_get_dialog (chat_window);
2040 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
2041 guint nb_rooms, nb_private;
2042 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2044 /* Skip the window if there aren't any rooms in it */
2045 if (room && nb_rooms == 0)
2048 /* Skip the window if there aren't any 1-1 chats in it */
2049 if (!room && nb_private == 0)
2052 /* Found a visible window on this desktop */
2061 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2063 EmpathyChatWindowPriv *priv;
2065 g_return_val_if_fail (window != NULL, NULL);
2067 priv = GET_PRIV (window);
2069 return priv->dialog;
2073 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2076 EmpathyChatWindowPriv *priv;
2078 GtkWidget *popup_label;
2080 GValue value = { 0, };
2082 g_return_if_fail (window != NULL);
2083 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2085 priv = GET_PRIV (window);
2087 /* Reference the chat object */
2088 g_object_ref (chat);
2090 /* If this window has just been created, position it */
2091 if (priv->chats == NULL) {
2092 const gchar *name = "chat-window";
2093 gboolean separate_windows;
2095 empathy_conf_get_bool (empathy_conf_get (),
2096 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
2099 if (separate_windows) {
2100 name = empathy_chat_get_id (chat);
2102 else if (empathy_chat_is_room (chat)) {
2103 name = "room-window";
2106 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2109 child = GTK_WIDGET (chat);
2110 label = chat_window_create_label (window, chat, TRUE);
2111 popup_label = chat_window_create_label (window, chat, FALSE);
2112 gtk_widget_show (child);
2114 g_signal_connect (chat, "notify::name",
2115 G_CALLBACK (chat_window_chat_notify_cb),
2117 g_signal_connect (chat, "notify::subject",
2118 G_CALLBACK (chat_window_chat_notify_cb),
2120 g_signal_connect (chat, "notify::remote-contact",
2121 G_CALLBACK (chat_window_chat_notify_cb),
2123 chat_window_chat_notify_cb (chat);
2125 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2126 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2127 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2128 g_value_init (&value, G_TYPE_BOOLEAN);
2129 g_value_set_boolean (&value, TRUE);
2130 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2131 child, "tab-expand" , &value);
2132 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2133 child, "tab-fill" , &value);
2134 g_value_unset (&value);
2136 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2140 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2143 EmpathyChatWindowPriv *priv;
2145 EmpathyContact *remote_contact;
2146 EmpathyChatManager *chat_manager;
2148 g_return_if_fail (window != NULL);
2149 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2151 priv = GET_PRIV (window);
2153 g_signal_handlers_disconnect_by_func (chat,
2154 chat_window_chat_notify_cb,
2156 remote_contact = g_object_get_data (G_OBJECT (chat),
2157 "chat-window-remote-contact");
2158 if (remote_contact) {
2159 g_signal_handlers_disconnect_by_func (remote_contact,
2160 chat_window_update_chat_tab,
2164 chat_manager = empathy_chat_manager_dup_singleton ();
2165 empathy_chat_manager_closed_chat (chat_manager, chat);
2166 g_object_unref (chat_manager);
2168 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2170 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2172 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2174 g_object_unref (chat);
2178 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2179 EmpathyChatWindow *new_window,
2184 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2185 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2186 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2188 widget = GTK_WIDGET (chat);
2190 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2191 G_OBJECT (widget)->ref_count);
2193 /* We reference here to make sure we don't loose the widget
2194 * and the EmpathyChat object during the move.
2196 g_object_ref (chat);
2197 g_object_ref (widget);
2199 empathy_chat_window_remove_chat (old_window, chat);
2200 empathy_chat_window_add_chat (new_window, chat);
2202 g_object_unref (widget);
2203 g_object_unref (chat);
2207 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2210 EmpathyChatWindowPriv *priv;
2213 g_return_if_fail (window != NULL);
2214 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2216 priv = GET_PRIV (window);
2218 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2220 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2225 empathy_chat_window_has_focus (EmpathyChatWindow *window)
2227 EmpathyChatWindowPriv *priv;
2230 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
2232 priv = GET_PRIV (window);
2234 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
2240 empathy_chat_window_find_chat (TpAccount *account,
2245 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2247 for (l = chat_windows; l; l = l->next) {
2248 EmpathyChatWindowPriv *priv;
2249 EmpathyChatWindow *window;
2253 priv = GET_PRIV (window);
2255 for (ll = priv->chats; ll; ll = ll->next) {
2260 if (account == empathy_chat_get_account (chat) &&
2261 !tp_strdiff (id, empathy_chat_get_id (chat))) {
2271 empathy_chat_window_present_chat (EmpathyChat *chat,
2274 EmpathyChatWindow *window;
2275 EmpathyChatWindowPriv *priv;
2277 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2279 window = chat_window_find_chat (chat);
2281 /* If the chat has no window, create one */
2282 if (window == NULL) {
2283 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2285 window = empathy_chat_window_new ();
2288 empathy_chat_window_add_chat (window, chat);
2291 /* Don't force the window to show itself when it wasn't
2292 * an action by the user
2294 if (timestamp == EMPATHY_DISPATCHER_NON_USER_ACTION)
2297 priv = GET_PRIV (window);
2298 empathy_chat_window_switch_to_chat (window, chat);
2299 empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2300 CLAMP (timestamp, 0, G_MAXUINT32));
2302 gtk_widget_grab_focus (chat->input_text_view);
2306 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2310 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2312 guint _nb_rooms = 0, _nb_private = 0;
2314 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2315 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2321 if (nb_rooms != NULL)
2322 *nb_rooms = _nb_rooms;
2323 if (nb_private != NULL)
2324 *nb_private = _nb_private;