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>
66 EmpathyChatWindow *window;
70 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
72 EmpathyChat *current_chat;
75 GList *chats_composing;
77 gboolean dnd_same_window;
78 EmpathyChatroomManager *chatroom_manager;
79 EmpathyNotifyManager *notify_mgr;
82 NotifyNotification *notification;
83 NotificationData *notification_data;
85 GtkTargetList *contact_targets;
86 GtkTargetList *file_targets;
88 EmpathyChatManager *chat_manager;
89 gulong chat_manager_chats_changed_id;
92 GtkUIManager *ui_manager;
93 GtkAction *menu_conv_insert_smiley;
94 GtkAction *menu_conv_favorite;
95 GtkAction *menu_conv_toggle_contacts;
97 GtkAction *menu_edit_cut;
98 GtkAction *menu_edit_copy;
99 GtkAction *menu_edit_paste;
100 GtkAction *menu_edit_find;
102 GtkAction *menu_tabs_next;
103 GtkAction *menu_tabs_prev;
104 GtkAction *menu_tabs_undo_close_tab;
105 GtkAction *menu_tabs_left;
106 GtkAction *menu_tabs_right;
107 GtkAction *menu_tabs_detach;
108 } EmpathyChatWindowPriv;
110 static GList *chat_windows = NULL;
112 static const guint tab_accel_keys[] = {
113 GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
114 GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
118 DND_DRAG_TYPE_CONTACT_ID,
119 DND_DRAG_TYPE_URI_LIST,
123 static const GtkTargetEntry drag_types_dest[] = {
124 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
125 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
126 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
127 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
130 static const GtkTargetEntry drag_types_dest_contact[] = {
131 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
134 static const GtkTargetEntry drag_types_dest_file[] = {
135 /* must be first to be prioritized, in order to receive the
136 * note's file path from Tomboy instead of an URI */
137 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
138 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
141 static void chat_window_update (EmpathyChatWindow *window);
143 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
146 chat_window_accel_cb (GtkAccelGroup *accelgroup,
150 EmpathyChatWindow *window)
152 EmpathyChatWindowPriv *priv;
156 priv = GET_PRIV (window);
158 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
159 if (tab_accel_keys[i] == key) {
166 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
170 static EmpathyChatWindow *
171 chat_window_find_chat (EmpathyChat *chat)
173 EmpathyChatWindowPriv *priv;
176 for (l = chat_windows; l; l = l->next) {
177 priv = GET_PRIV (l->data);
178 ll = g_list_find (priv->chats, chat);
188 chat_window_close_clicked_cb (GtkAction *action,
191 EmpathyChatWindow *window;
193 window = chat_window_find_chat (chat);
194 empathy_chat_window_remove_chat (window, chat);
198 chat_tab_style_set_cb (GtkWidget *hbox,
199 GtkStyle *previous_style,
203 int char_width, h, w;
204 PangoContext *context;
205 PangoFontMetrics *metrics;
207 button = g_object_get_data (G_OBJECT (user_data),
208 "chat-window-tab-close-button");
209 context = gtk_widget_get_pango_context (hbox);
211 metrics = pango_context_get_metrics (context, gtk_widget_get_style (hbox)->font_desc,
212 pango_context_get_language (context));
213 char_width = pango_font_metrics_get_approximate_char_width (metrics);
214 pango_font_metrics_unref (metrics);
216 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
217 GTK_ICON_SIZE_MENU, &w, &h);
219 /* Request at least about 12 chars width plus at least space for the status
220 * image and the close button */
221 gtk_widget_set_size_request (hbox,
222 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
224 gtk_widget_set_size_request (button, w, h);
228 chat_window_create_label (EmpathyChatWindow *window,
230 gboolean is_tab_label)
232 EmpathyChatWindowPriv *priv;
234 GtkWidget *name_label;
235 GtkWidget *status_image;
236 GtkWidget *close_button;
237 GtkWidget *close_image;
238 GtkWidget *event_box;
239 GtkWidget *event_box_hbox;
240 PangoAttrList *attr_list;
241 PangoAttribute *attr;
243 priv = GET_PRIV (window);
245 /* The spacing between the button and the label. */
246 hbox = gtk_hbox_new (FALSE, 0);
248 event_box = gtk_event_box_new ();
249 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
251 name_label = gtk_label_new (NULL);
253 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
255 attr_list = pango_attr_list_new ();
256 attr = pango_attr_scale_new (1/1.2);
257 attr->start_index = 0;
258 attr->end_index = -1;
259 pango_attr_list_insert (attr_list, attr);
260 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
261 pango_attr_list_unref (attr_list);
263 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
264 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
265 g_object_set_data (G_OBJECT (chat),
266 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
269 status_image = gtk_image_new ();
271 /* Spacing between the icon and label. */
272 event_box_hbox = gtk_hbox_new (FALSE, 0);
274 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
275 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
277 g_object_set_data (G_OBJECT (chat),
278 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
280 g_object_set_data (G_OBJECT (chat),
281 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
284 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
285 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
288 close_button = gtk_button_new ();
289 gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
290 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
292 /* We don't want focus/keynav for the button to avoid clutter, and
293 * Ctrl-W works anyway.
295 gtk_widget_set_can_focus (close_button, FALSE);
296 gtk_widget_set_can_default (close_button, FALSE);
298 /* Set the name to make the special rc style match. */
299 gtk_widget_set_name (close_button, "empathy-close-button");
301 close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
303 gtk_container_add (GTK_CONTAINER (close_button), close_image);
305 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
307 g_signal_connect (close_button,
309 G_CALLBACK (chat_window_close_clicked_cb),
312 /* React to theme changes and also setup the size correctly. */
313 g_signal_connect (hbox,
315 G_CALLBACK (chat_tab_style_set_cb),
319 gtk_widget_show_all (hbox);
325 _submenu_notify_visible_changed_cb (GObject *object,
329 g_signal_handlers_disconnect_by_func (object,
330 _submenu_notify_visible_changed_cb,
332 chat_window_update (EMPATHY_CHAT_WINDOW (userdata));
336 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
341 gboolean wrap_around;
342 gboolean is_connected;
345 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
346 first_page = (page_num == 0);
347 last_page = (page_num == (num_pages - 1));
348 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
350 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
352 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
354 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
356 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
357 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
358 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
359 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
363 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
364 EmpathyChatWindow *self)
366 EmpathyTpChat *tp_chat;
367 TpConnection *connection;
369 gboolean sensitive = FALSE;
371 g_return_if_fail (priv->current_chat != NULL);
373 action = gtk_ui_manager_get_action (priv->ui_manager,
374 "/chats_menubar/menu_conv/menu_conv_invite_participant");
375 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
377 if (tp_chat != NULL) {
378 connection = empathy_tp_chat_get_connection (tp_chat);
380 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
381 (tp_connection_get_status (connection, NULL) ==
382 TP_CONNECTION_STATUS_CONNECTED);
385 gtk_action_set_sensitive (action, sensitive);
389 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
390 EmpathyChatWindow *window)
392 GtkWidget *menu, *submenu, *orig_submenu;
394 menu = gtk_ui_manager_get_widget (priv->ui_manager,
395 "/chats_menubar/menu_contact");
396 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
398 if (orig_submenu == NULL || !GTK_WIDGET_VISIBLE (orig_submenu)) {
399 submenu = empathy_chat_get_contact_menu (priv->current_chat);
400 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
401 gtk_widget_show (menu);
403 empathy_signal_connect_weak (orig_submenu,
405 (GCallback)_submenu_notify_visible_changed_cb,
411 get_all_unread_messages (EmpathyChatWindowPriv *priv)
416 for (l = priv->chats_new_msg; l != NULL; l = g_list_next (l)) {
417 EmpathyChat *chat = l->data;
419 nb += empathy_chat_get_nb_unread_messages (chat);
426 get_window_title_name (EmpathyChatWindowPriv *priv)
428 const gchar *active_name;
430 guint current_unread_msgs;
432 nb_chats = g_list_length (priv->chats);
433 g_assert (nb_chats > 0);
435 active_name = empathy_chat_get_name (priv->current_chat);
437 current_unread_msgs = empathy_chat_get_nb_unread_messages (
442 if (current_unread_msgs == 0)
443 return g_strdup (active_name);
445 return g_strdup_printf (ngettext (
447 "%s (%d unread)", current_unread_msgs),
448 active_name, current_unread_msgs);
450 guint nb_others = nb_chats - 1;
451 guint all_unread_msgs;
453 all_unread_msgs = get_all_unread_messages (priv);
455 if (all_unread_msgs == 0) {
456 /* no unread message */
457 return g_strdup_printf (ngettext (
459 "%s (and %u others)", nb_others),
460 active_name, nb_others);
463 else if (all_unread_msgs == current_unread_msgs) {
464 /* unread messages are in the current tab */
465 return g_strdup_printf (ngettext (
467 "%s (%d unread)", current_unread_msgs),
468 active_name, current_unread_msgs);
471 else if (current_unread_msgs == 0) {
472 /* unread messages are in other tabs */
473 return g_strdup_printf (ngettext (
474 "%s (%d unread from others)",
475 "%s (%d unread from others)",
477 active_name, all_unread_msgs);
481 /* unread messages are in all the tabs */
482 return g_strdup_printf (ngettext (
483 "%s (%d unread from all)",
484 "%s (%d unread from all)",
486 active_name, all_unread_msgs);
492 chat_window_title_update (EmpathyChatWindowPriv *priv)
496 name = get_window_title_name (priv);
497 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
502 chat_window_icon_update (EmpathyChatWindowPriv *priv)
505 EmpathyContact *remote_contact;
506 gboolean avatar_in_icon;
509 n_chats = g_list_length (priv->chats);
511 /* Update window icon */
512 if (priv->chats_new_msg) {
513 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
514 EMPATHY_IMAGE_MESSAGE);
516 empathy_conf_get_bool (empathy_conf_get (),
517 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON,
520 if (n_chats == 1 && avatar_in_icon) {
521 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
522 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
523 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
526 g_object_unref (icon);
529 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
535 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
539 GtkWidget *chat_close_button;
542 if (num_pages == 1) {
543 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
544 chat_close_button = g_object_get_data (G_OBJECT (chat),
545 "chat-window-tab-close-button");
546 gtk_widget_hide (chat_close_button);
548 for (i=0; i<num_pages; i++) {
549 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
550 chat_close_button = g_object_get_data (G_OBJECT (chat),
551 "chat-window-tab-close-button");
552 gtk_widget_show (chat_close_button);
558 chat_window_update (EmpathyChatWindow *window)
560 EmpathyChatWindowPriv *priv = GET_PRIV (window);
563 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
565 /* Update Tab menu */
566 chat_window_menu_context_update (priv,
569 chat_window_conversation_menu_update (priv, window);
571 chat_window_contact_menu_update (priv,
574 chat_window_title_update (priv);
576 chat_window_icon_update (priv);
578 chat_window_close_button_update (priv,
583 append_markup_printf (GString *string,
590 va_start (args, format);
592 tmp = g_markup_vprintf_escaped (format, args);
593 g_string_append (string, tmp);
600 chat_window_update_chat_tab (EmpathyChat *chat)
602 EmpathyChatWindow *window;
603 EmpathyChatWindowPriv *priv;
604 EmpathyContact *remote_contact;
608 const gchar *subject;
609 const gchar *status = NULL;
613 const gchar *icon_name;
614 GtkWidget *tab_image;
615 GtkWidget *menu_image;
617 window = chat_window_find_chat (chat);
621 priv = GET_PRIV (window);
623 /* Get information */
624 name = empathy_chat_get_name (chat);
625 account = empathy_chat_get_account (chat);
626 subject = empathy_chat_get_subject (chat);
627 remote_contact = empathy_chat_get_remote_contact (chat);
629 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
630 name, tp_proxy_get_object_path (account), subject, remote_contact);
632 /* Update tab image */
633 if (empathy_chat_get_tp_chat (chat) == NULL) {
634 /* No TpChat, we are disconnected */
637 else if (g_list_find (priv->chats_new_msg, chat)) {
638 icon_name = EMPATHY_IMAGE_MESSAGE;
640 else if (g_list_find (priv->chats_composing, chat)) {
641 icon_name = EMPATHY_IMAGE_TYPING;
643 else if (remote_contact) {
644 icon_name = empathy_icon_name_for_contact (remote_contact);
646 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
649 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
650 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
651 if (icon_name != NULL) {
652 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
653 gtk_widget_show (tab_image);
654 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
655 gtk_widget_show (menu_image);
657 gtk_widget_hide (tab_image);
658 gtk_widget_hide (menu_image);
661 /* Update tab tooltip */
662 tooltip = g_string_new (NULL);
664 if (remote_contact) {
665 id = empathy_contact_get_id (remote_contact);
666 status = empathy_contact_get_presence_message (remote_contact);
671 append_markup_printf (tooltip,
672 "<b>%s</b><small> (%s)</small>",
674 tp_account_get_display_name (account));
676 if (!EMP_STR_EMPTY (status)) {
677 append_markup_printf (tooltip, "\n<i>%s</i>", status);
681 append_markup_printf (tooltip, "\n<b>%s</b> %s",
682 _("Topic:"), subject);
685 if (g_list_find (priv->chats_composing, chat)) {
686 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
689 markup = g_string_free (tooltip, FALSE);
690 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
691 gtk_widget_set_tooltip_markup (widget, markup);
692 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
693 gtk_widget_set_tooltip_markup (widget, markup);
696 /* Update tab and menu label */
697 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
698 gtk_label_set_text (GTK_LABEL (widget), name);
699 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
700 gtk_label_set_text (GTK_LABEL (widget), name);
702 /* Update the window if it's the current chat */
703 if (priv->current_chat == chat) {
704 chat_window_update (window);
709 chat_window_chat_notify_cb (EmpathyChat *chat)
711 EmpathyContact *old_remote_contact;
712 EmpathyContact *remote_contact = NULL;
714 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
715 remote_contact = empathy_chat_get_remote_contact (chat);
717 if (old_remote_contact != remote_contact) {
718 /* The remote-contact associated with the chat changed, we need
719 * to keep track of any change of that contact and update the
720 * window each time. */
721 if (remote_contact) {
722 g_signal_connect_swapped (remote_contact, "notify",
723 G_CALLBACK (chat_window_update_chat_tab),
726 if (old_remote_contact) {
727 g_signal_handlers_disconnect_by_func (old_remote_contact,
728 chat_window_update_chat_tab,
732 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
733 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
736 chat_window_update_chat_tab (chat);
740 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
741 EmpathySmiley *smiley,
744 EmpathyChatWindowPriv *priv = GET_PRIV (window);
746 GtkTextBuffer *buffer;
749 chat = priv->current_chat;
751 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
752 gtk_text_buffer_get_end_iter (buffer, &iter);
753 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
757 chat_window_conv_activate_cb (GtkAction *action,
758 EmpathyChatWindow *window)
760 EmpathyChatWindowPriv *priv = GET_PRIV (window);
763 EmpathyContact *remote_contact = NULL;
765 /* Favorite room menu */
766 is_room = empathy_chat_is_room (priv->current_chat);
770 gboolean found = FALSE;
771 EmpathyChatroom *chatroom;
773 room = empathy_chat_get_id (priv->current_chat);
774 account = empathy_chat_get_account (priv->current_chat);
775 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
777 if (chatroom != NULL)
778 found = empathy_chatroom_is_favorite (chatroom);
780 DEBUG ("This room %s favorite", found ? "is" : "is not");
781 gtk_toggle_action_set_active (
782 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
784 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
786 /* Show contacts menu */
787 g_object_get (priv->current_chat,
788 "remote-contact", &remote_contact,
789 "show-contacts", &active,
791 if (remote_contact == NULL) {
792 gtk_toggle_action_set_active (
793 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
796 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
797 (remote_contact == NULL));
798 if (remote_contact != NULL) {
799 g_object_unref (remote_contact);
804 chat_window_clear_activate_cb (GtkAction *action,
805 EmpathyChatWindow *window)
807 EmpathyChatWindowPriv *priv = GET_PRIV (window);
809 empathy_chat_clear (priv->current_chat);
813 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
814 EmpathyChatWindow *window)
816 EmpathyChatWindowPriv *priv = GET_PRIV (window);
820 EmpathyChatroom *chatroom;
822 active = gtk_toggle_action_get_active (toggle_action);
823 account = empathy_chat_get_account (priv->current_chat);
824 room = empathy_chat_get_id (priv->current_chat);
826 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
829 if (chatroom == NULL) {
832 name = empathy_chat_get_name (priv->current_chat);
833 chatroom = empathy_chatroom_new_full (account, room, name, FALSE);
834 empathy_chatroom_manager_add (priv->chatroom_manager, chatroom);
835 g_object_unref (chatroom);
838 empathy_chatroom_set_favorite (chatroom, active);
842 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
843 EmpathyChatWindow *window)
845 EmpathyChatWindowPriv *priv = GET_PRIV (window);
848 active = gtk_toggle_action_get_active (toggle_action);
850 empathy_chat_set_show_contacts (priv->current_chat, active);
854 got_contact_cb (EmpathyTpContactFactory *factory,
855 EmpathyContact *contact,
860 EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data);
863 DEBUG ("Failed: %s", error->message);
866 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
867 contact, _("Inviting you to this room"));
872 chat_window_invite_participant_activate_cb (GtkAction *action,
873 EmpathyChatWindow *window)
875 EmpathyChatWindowPriv *priv;
877 EmpathyTpChat *tp_chat;
882 priv = GET_PRIV (window);
884 g_return_if_fail (priv->current_chat != NULL);
886 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
887 channel = empathy_tp_chat_get_channel (tp_chat);
888 account = empathy_chat_get_account (priv->current_chat);
890 dialog = empathy_invite_participant_dialog_new (
891 GTK_WINDOW (priv->dialog), account);
892 gtk_widget_show (dialog);
894 response = gtk_dialog_run (GTK_DIALOG (dialog));
896 if (response == GTK_RESPONSE_ACCEPT) {
897 TpConnection *connection;
898 EmpathyTpContactFactory *factory;
901 id = empathy_contact_selector_dialog_get_selected (
902 EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL);
903 if (EMP_STR_EMPTY (id)) goto out;
905 connection = tp_channel_borrow_connection (channel);
906 factory = empathy_tp_contact_factory_dup_singleton (connection);
908 empathy_tp_contact_factory_get_from_id (factory, id,
909 got_contact_cb, tp_chat, NULL, NULL);
911 g_object_unref (factory);
915 gtk_widget_destroy (dialog);
919 chat_window_close_activate_cb (GtkAction *action,
920 EmpathyChatWindow *window)
922 EmpathyChatWindowPriv *priv;
924 priv = GET_PRIV (window);
926 g_return_if_fail (priv->current_chat != NULL);
928 empathy_chat_window_remove_chat (window, priv->current_chat);
932 chat_window_edit_activate_cb (GtkAction *action,
933 EmpathyChatWindow *window)
935 EmpathyChatWindowPriv *priv;
936 GtkClipboard *clipboard;
937 GtkTextBuffer *buffer;
938 gboolean text_available;
940 priv = GET_PRIV (window);
942 g_return_if_fail (priv->current_chat != NULL);
944 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
945 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
946 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
947 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
951 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
952 if (gtk_text_buffer_get_has_selection (buffer)) {
953 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
954 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
958 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
960 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
961 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
964 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
965 text_available = gtk_clipboard_wait_is_text_available (clipboard);
966 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
970 chat_window_cut_activate_cb (GtkAction *action,
971 EmpathyChatWindow *window)
973 EmpathyChatWindowPriv *priv;
975 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
977 priv = GET_PRIV (window);
979 empathy_chat_cut (priv->current_chat);
983 chat_window_copy_activate_cb (GtkAction *action,
984 EmpathyChatWindow *window)
986 EmpathyChatWindowPriv *priv;
988 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
990 priv = GET_PRIV (window);
992 empathy_chat_copy (priv->current_chat);
996 chat_window_paste_activate_cb (GtkAction *action,
997 EmpathyChatWindow *window)
999 EmpathyChatWindowPriv *priv;
1001 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1003 priv = GET_PRIV (window);
1005 empathy_chat_paste (priv->current_chat);
1009 chat_window_find_activate_cb (GtkAction *action,
1010 EmpathyChatWindow *window)
1012 EmpathyChatWindowPriv *priv;
1014 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1016 priv = GET_PRIV (window);
1018 empathy_chat_find (priv->current_chat);
1022 chat_window_tabs_next_activate_cb (GtkAction *action,
1023 EmpathyChatWindow *window)
1025 EmpathyChatWindowPriv *priv;
1027 gint index_, numPages;
1028 gboolean wrap_around;
1030 priv = GET_PRIV (window);
1032 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1033 &wrap_around, NULL);
1035 chat = priv->current_chat;
1036 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1037 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1039 if (index_ == (numPages - 1) && wrap_around) {
1040 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1044 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1048 chat_window_tabs_previous_activate_cb (GtkAction *action,
1049 EmpathyChatWindow *window)
1051 EmpathyChatWindowPriv *priv;
1053 gint index_, numPages;
1054 gboolean wrap_around;
1056 priv = GET_PRIV (window);
1058 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1059 &wrap_around, NULL);
1061 chat = priv->current_chat;
1062 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1063 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1065 if (index_ <= 0 && wrap_around) {
1066 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1070 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1074 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1075 EmpathyChatWindow *window)
1077 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1078 empathy_chat_manager_undo_closed_chat (priv->chat_manager);
1082 chat_window_tabs_left_activate_cb (GtkAction *action,
1083 EmpathyChatWindow *window)
1085 EmpathyChatWindowPriv *priv;
1089 priv = GET_PRIV (window);
1091 chat = priv->current_chat;
1092 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1097 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1103 chat_window_tabs_right_activate_cb (GtkAction *action,
1104 EmpathyChatWindow *window)
1106 EmpathyChatWindowPriv *priv;
1110 priv = GET_PRIV (window);
1112 chat = priv->current_chat;
1113 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1115 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
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 free_notification_data (NotificationData *data)
1207 g_object_unref (data->chat);
1208 g_slice_free (NotificationData, data);
1212 chat_window_notification_closed_cb (NotifyNotification *notify,
1213 NotificationData *cb_data)
1215 EmpathyNotificationClosedReason reason = 0;
1216 EmpathyChatWindowPriv *priv = GET_PRIV (cb_data->window);
1218 #ifdef notify_notification_get_closed_reason
1219 reason = notify_notification_get_closed_reason (notify);
1221 if (reason == EMPATHY_NOTIFICATION_CLOSED_DISMISSED) {
1222 empathy_chat_window_present_chat (cb_data->chat);
1225 g_object_unref (notify);
1226 free_notification_data (cb_data);
1228 if (priv->notification == notify) {
1229 priv->notification = NULL;
1230 priv->notification_data = NULL;
1235 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1236 EmpathyMessage *message,
1239 EmpathyContact *sender;
1240 const gchar *header;
1244 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1245 gboolean res, has_x_canonical_append;
1246 NotifyNotification *notification = priv->notification;
1248 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1251 empathy_conf_get_bool (empathy_conf_get (),
1252 EMPATHY_PREFS_NOTIFICATIONS_FOCUS, &res);
1258 sender = empathy_message_get_sender (message);
1259 header = empathy_contact_get_name (sender);
1260 body = empathy_message_get_body (message);
1261 escaped = g_markup_escape_text (body, -1);
1262 has_x_canonical_append = empathy_notify_manager_has_capability (
1263 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1265 if (notification != NULL && !has_x_canonical_append) {
1266 /* if the notification server supports x-canonical-append, it is
1267 better to not use notify_notification_update to avoid
1268 overwriting the current notification message */
1269 notify_notification_update (notification,
1270 header, escaped, NULL);
1272 NotificationData *cb_data = cb_data = g_slice_new0 (NotificationData);
1274 cb_data->chat = g_object_ref (chat);
1275 cb_data->window = window;
1277 /* if the notification server supports x-canonical-append,
1278 the hint will be added, so that the message from the
1279 just created notification will be automatically appended
1280 to an existing notification with the same title.
1281 In this way the previous message will not be lost: the new
1282 message will appear below it, in the same notification */
1283 notification = notify_notification_new (header, escaped, NULL, NULL);
1285 if (priv->notification == NULL) {
1286 priv->notification = notification;
1287 priv->notification_data = cb_data;
1290 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1292 g_signal_connect (notification, "closed",
1293 G_CALLBACK (chat_window_notification_closed_cb), cb_data);
1295 if (has_x_canonical_append) {
1296 notify_notification_set_hint_string (notification,
1297 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
1301 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1302 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1304 if (pixbuf != NULL) {
1305 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1306 g_object_unref (pixbuf);
1309 notify_notification_show (notification, NULL);
1315 chat_window_set_highlight_room_tab_label (EmpathyChat *chat)
1320 if (!empathy_chat_is_room (chat))
1323 markup = g_markup_printf_escaped (
1324 "<span color=\"red\" weight=\"bold\">%s</span>",
1325 empathy_chat_get_name (chat));
1327 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1328 gtk_label_set_markup (GTK_LABEL (widget), markup);
1333 chat_window_new_message_cb (EmpathyChat *chat,
1334 EmpathyMessage *message,
1335 EmpathyChatWindow *window)
1337 EmpathyChatWindowPriv *priv;
1339 gboolean needs_urgency;
1340 EmpathyContact *sender;
1342 priv = GET_PRIV (window);
1344 has_focus = empathy_chat_window_has_focus (window);
1346 /* - if we're the sender, we play the sound if it's specified in the
1347 * preferences and we're not away.
1348 * - if we receive a message, we play the sound if it's specified in the
1349 * preferences and the window does not have focus on the chat receiving
1353 sender = empathy_message_get_sender (message);
1355 if (empathy_contact_is_user (sender)) {
1356 empathy_sound_play (GTK_WIDGET (priv->dialog),
1357 EMPATHY_SOUND_MESSAGE_OUTGOING);
1360 if (has_focus && priv->current_chat == chat) {
1361 /* window and tab are focused so consider the message to be read */
1363 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1364 empathy_chat_messages_read (chat);
1368 if (!g_list_find (priv->chats_new_msg, chat)) {
1369 priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
1370 chat_window_update_chat_tab (chat);
1373 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1374 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1375 * an unamed MUC (msn-like).
1376 * In case of a MUC, we set urgency only if the message contains our
1378 if (empathy_chat_is_room (chat) ||
1379 empathy_chat_get_remote_contact (chat) == NULL) {
1380 needs_urgency = empathy_message_should_highlight (message);
1382 needs_urgency = TRUE;
1385 if (needs_urgency) {
1387 chat_window_set_urgency_hint (window, TRUE);
1388 chat_window_set_highlight_room_tab_label (chat);
1391 empathy_sound_play (GTK_WIDGET (priv->dialog),
1392 EMPATHY_SOUND_MESSAGE_INCOMING);
1393 chat_window_show_or_update_notification (window, message, chat);
1396 /* update the number of unread messages */
1397 chat_window_title_update (priv);
1400 static GtkNotebook *
1401 chat_window_detach_hook (GtkNotebook *source,
1407 EmpathyChatWindowPriv *priv;
1408 EmpathyChatWindow *window, *new_window;
1411 chat = EMPATHY_CHAT (page);
1412 window = chat_window_find_chat (chat);
1414 new_window = empathy_chat_window_new ();
1415 priv = GET_PRIV (new_window);
1417 DEBUG ("Detach hook called");
1419 empathy_chat_window_move_chat (window, new_window, chat);
1421 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1422 gtk_widget_show (priv->dialog);
1428 chat_window_page_switched_cb (GtkNotebook *notebook,
1429 GtkNotebookPage *page,
1431 EmpathyChatWindow *window)
1433 EmpathyChatWindowPriv *priv;
1437 DEBUG ("Page switched");
1439 priv = GET_PRIV (window);
1441 child = gtk_notebook_get_nth_page (notebook, page_num);
1442 chat = EMPATHY_CHAT (child);
1444 if (priv->page_added) {
1445 priv->page_added = FALSE;
1446 empathy_chat_scroll_down (chat);
1448 else if (priv->current_chat == chat) {
1452 priv->current_chat = chat;
1453 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1454 empathy_chat_messages_read (chat);
1456 chat_window_update_chat_tab (chat);
1460 chat_window_page_added_cb (GtkNotebook *notebook,
1463 EmpathyChatWindow *window)
1465 EmpathyChatWindowPriv *priv;
1468 priv = GET_PRIV (window);
1470 /* If we just received DND to the same window, we don't want
1471 * to do anything here like removing the tab and then readding
1472 * it, so we return here and in "page-added".
1474 if (priv->dnd_same_window) {
1475 DEBUG ("Page added (back to the same window)");
1476 priv->dnd_same_window = FALSE;
1480 DEBUG ("Page added");
1482 /* Get chat object */
1483 chat = EMPATHY_CHAT (child);
1485 /* Connect chat signals for this window */
1486 g_signal_connect (chat, "composing",
1487 G_CALLBACK (chat_window_composing_cb),
1489 g_signal_connect (chat, "new-message",
1490 G_CALLBACK (chat_window_new_message_cb),
1492 g_signal_connect (chat, "notify::tp-chat",
1493 G_CALLBACK (chat_window_update_chat_tab),
1496 /* Set flag so we know to perform some special operations on
1497 * switch page due to the new page being added.
1499 priv->page_added = TRUE;
1501 /* Get list of chats up to date */
1502 priv->chats = g_list_append (priv->chats, chat);
1504 chat_window_update_chat_tab (chat);
1508 chat_window_page_removed_cb (GtkNotebook *notebook,
1511 EmpathyChatWindow *window)
1513 EmpathyChatWindowPriv *priv;
1516 priv = GET_PRIV (window);
1518 /* If we just received DND to the same window, we don't want
1519 * to do anything here like removing the tab and then readding
1520 * it, so we return here and in "page-added".
1522 if (priv->dnd_same_window) {
1523 DEBUG ("Page removed (and will be readded to same window)");
1527 DEBUG ("Page removed");
1529 /* Get chat object */
1530 chat = EMPATHY_CHAT (child);
1532 /* Disconnect all signal handlers for this chat and this window */
1533 g_signal_handlers_disconnect_by_func (chat,
1534 G_CALLBACK (chat_window_composing_cb),
1536 g_signal_handlers_disconnect_by_func (chat,
1537 G_CALLBACK (chat_window_new_message_cb),
1539 g_signal_handlers_disconnect_by_func (chat,
1540 G_CALLBACK (chat_window_update_chat_tab),
1543 /* Keep list of chats up to date */
1544 priv->chats = g_list_remove (priv->chats, chat);
1545 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1546 empathy_chat_messages_read (chat);
1547 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1549 if (priv->chats == NULL) {
1550 g_object_unref (window);
1552 chat_window_update (window);
1557 chat_window_focus_in_event_cb (GtkWidget *widget,
1559 EmpathyChatWindow *window)
1561 EmpathyChatWindowPriv *priv;
1563 priv = GET_PRIV (window);
1565 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
1566 empathy_chat_messages_read (priv->current_chat);
1568 chat_window_set_urgency_hint (window, FALSE);
1570 /* Update the title, since we now mark all unread messages as read. */
1571 chat_window_update_chat_tab (priv->current_chat);
1577 chat_window_drag_drop (GtkWidget *widget,
1578 GdkDragContext *context,
1582 EmpathyChatWindow *window)
1585 EmpathyChatWindowPriv *priv;
1587 priv = GET_PRIV (window);
1589 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1590 if (target == GDK_NONE)
1591 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1593 if (target != GDK_NONE) {
1594 gtk_drag_get_data (widget, context, target, time_);
1602 chat_window_drag_motion (GtkWidget *widget,
1603 GdkDragContext *context,
1607 EmpathyChatWindow *window)
1610 EmpathyChatWindowPriv *priv;
1612 priv = GET_PRIV (window);
1614 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1615 if (target != GDK_NONE) {
1616 /* This is a file drag. Ensure the contact is online and set the
1617 drag type to COPY. Note that it's possible that the tab will
1618 be switched by GTK+ after a timeout from drag_motion without
1619 getting another drag_motion to disable the drop. You have
1620 to hold your mouse really still.
1622 EmpathyContact *contact;
1624 priv = GET_PRIV (window);
1625 contact = empathy_chat_get_remote_contact (priv->current_chat);
1626 /* contact is NULL for multi-user chats. We don't do
1627 * file transfers to MUCs. We also don't send files
1628 * to offline contacts or contacts that don't support
1631 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1632 gdk_drag_status (context, 0, time_);
1635 if (!(empathy_contact_get_capabilities (contact)
1636 & EMPATHY_CAPABILITIES_FT)) {
1637 gdk_drag_status (context, 0, time_);
1640 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1644 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1645 if (target != GDK_NONE) {
1646 /* This is a drag of a contact from a contact list. Set to COPY.
1647 FIXME: If this drag is to a MUC window, it invites the user.
1648 Otherwise, it opens a chat. Should we use a different drag
1649 type for invites? Should we allow ASK?
1651 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1659 chat_window_drag_data_received (GtkWidget *widget,
1660 GdkDragContext *context,
1663 GtkSelectionData *selection,
1666 EmpathyChatWindow *window)
1668 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1669 EmpathyChat *chat = NULL;
1670 EmpathyChatWindow *old_window;
1671 TpAccount *account = NULL;
1672 TpAccountManager *account_manager;
1675 const gchar *account_id;
1676 const gchar *contact_id;
1678 id = (const gchar*) gtk_selection_data_get_data (selection);
1680 /* FIXME: Perhaps should be sure that the account manager is
1681 * prepared before calling _ensure_account on it. */
1682 account_manager = tp_account_manager_dup ();
1684 DEBUG ("DND contact from roster with id:'%s'", id);
1686 strv = g_strsplit (id, ":", 2);
1687 if (g_strv_length (strv) == 2) {
1688 account_id = strv[0];
1689 contact_id = strv[1];
1691 tp_account_manager_ensure_account (account_manager, account_id);
1692 if (account != NULL)
1693 chat = empathy_chat_window_find_chat (account, contact_id);
1696 if (account == NULL) {
1698 gtk_drag_finish (context, FALSE, FALSE, time_);
1703 TpConnection *connection;
1705 connection = tp_account_get_connection (account);
1708 empathy_dispatcher_chat_with_contact_id (
1709 connection, contact_id, NULL, NULL);
1715 g_object_unref (account_manager);
1718 old_window = chat_window_find_chat (chat);
1720 if (old_window == window) {
1721 gtk_drag_finish (context, TRUE, FALSE, time_);
1725 empathy_chat_window_move_chat (old_window, window, chat);
1727 empathy_chat_window_add_chat (window, chat);
1730 /* Added to take care of any outstanding chat events */
1731 empathy_chat_window_present_chat (chat);
1733 /* We should return TRUE to remove the data when doing
1734 * GDK_ACTION_MOVE, but we don't here otherwise it has
1735 * weird consequences, and we handle that internally
1736 * anyway with add_chat () and remove_chat ().
1738 gtk_drag_finish (context, TRUE, FALSE, time_);
1740 else if (info == DND_DRAG_TYPE_URI_LIST) {
1741 EmpathyChatWindowPriv *priv;
1742 EmpathyContact *contact;
1745 priv = GET_PRIV (window);
1746 contact = empathy_chat_get_remote_contact (priv->current_chat);
1748 /* contact is NULL when current_chat is a multi-user chat.
1749 * We don't do file transfers to MUCs, so just cancel the drag.
1751 if (contact == NULL) {
1752 gtk_drag_finish (context, TRUE, FALSE, time_);
1756 data = (const gchar *) gtk_selection_data_get_data (selection);
1757 empathy_send_file_from_uri_list (contact, data);
1759 gtk_drag_finish (context, TRUE, FALSE, time_);
1761 else if (info == DND_DRAG_TYPE_TAB) {
1763 EmpathyChatWindow *old_window = NULL;
1767 chat = (void *) gtk_selection_data_get_data (selection);
1768 old_window = chat_window_find_chat (*chat);
1771 EmpathyChatWindowPriv *priv;
1773 priv = GET_PRIV (window);
1774 priv->dnd_same_window = (old_window == window);
1775 DEBUG ("DND tab (within same window: %s)",
1776 priv->dnd_same_window ? "Yes" : "No");
1779 DEBUG ("DND from unknown source");
1780 gtk_drag_finish (context, FALSE, FALSE, time_);
1785 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1786 guint num_chats_in_manager,
1787 EmpathyChatWindow *window)
1789 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1791 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1792 num_chats_in_manager > 0);
1796 chat_window_finalize (GObject *object)
1798 EmpathyChatWindow *window;
1799 EmpathyChatWindowPriv *priv;
1801 window = EMPATHY_CHAT_WINDOW (object);
1802 priv = GET_PRIV (window);
1804 DEBUG ("Finalized: %p", object);
1806 g_object_unref (priv->ui_manager);
1807 g_object_unref (priv->chatroom_manager);
1808 g_object_unref (priv->notify_mgr);
1810 if (priv->notification != NULL) {
1811 notify_notification_close (priv->notification, NULL);
1812 priv->notification = NULL;
1813 free_notification_data (priv->notification_data);
1814 priv->notification_data = NULL;
1817 if (priv->contact_targets) {
1818 gtk_target_list_unref (priv->contact_targets);
1820 if (priv->file_targets) {
1821 gtk_target_list_unref (priv->file_targets);
1824 if (priv->chat_manager) {
1825 g_signal_handler_disconnect (priv->chat_manager,
1826 priv->chat_manager_chats_changed_id);
1827 g_object_unref (priv->chat_manager);
1828 priv->chat_manager = NULL;
1831 chat_windows = g_list_remove (chat_windows, window);
1832 gtk_widget_destroy (priv->dialog);
1834 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1838 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1840 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1842 object_class->finalize = chat_window_finalize;
1844 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1846 /* Set up a style for the close button with no focus padding. */
1847 gtk_rc_parse_string (
1848 "style \"empathy-close-button-style\"\n"
1850 " GtkWidget::focus-padding = 0\n"
1854 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1856 gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1860 empathy_chat_window_init (EmpathyChatWindow *window)
1863 GtkAccelGroup *accel_group;
1868 GtkWidget *chat_vbox;
1870 EmpathySmileyManager *smiley_manager;
1871 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1872 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1874 window->priv = priv;
1875 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1876 gui = empathy_builder_get_file (filename,
1877 "chat_window", &priv->dialog,
1878 "chat_vbox", &chat_vbox,
1879 "ui_manager", &priv->ui_manager,
1880 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1881 "menu_conv_favorite", &priv->menu_conv_favorite,
1882 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1883 "menu_edit_cut", &priv->menu_edit_cut,
1884 "menu_edit_copy", &priv->menu_edit_copy,
1885 "menu_edit_paste", &priv->menu_edit_paste,
1886 "menu_edit_find", &priv->menu_edit_find,
1887 "menu_tabs_next", &priv->menu_tabs_next,
1888 "menu_tabs_prev", &priv->menu_tabs_prev,
1889 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1890 "menu_tabs_left", &priv->menu_tabs_left,
1891 "menu_tabs_right", &priv->menu_tabs_right,
1892 "menu_tabs_detach", &priv->menu_tabs_detach,
1896 empathy_builder_connect (gui, window,
1897 "menu_conv", "activate", chat_window_conv_activate_cb,
1898 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1899 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1900 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1901 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1902 "menu_conv_close", "activate", chat_window_close_activate_cb,
1903 "menu_edit", "activate", chat_window_edit_activate_cb,
1904 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1905 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1906 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1907 "menu_edit_find", "activate", chat_window_find_activate_cb,
1908 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1909 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1910 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1911 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1912 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1913 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1914 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1915 "menu_help_about", "activate", chat_window_help_about_activate_cb,
1918 g_object_ref (priv->ui_manager);
1919 g_object_unref (gui);
1921 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1923 priv->notebook = gtk_notebook_new ();
1924 gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
1925 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
1926 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
1927 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1928 gtk_widget_show (priv->notebook);
1931 accel_group = gtk_accel_group_new ();
1932 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
1934 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
1935 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
1938 gtk_accel_group_connect (accel_group,
1945 g_object_unref (accel_group);
1947 /* Set up drag target lists */
1948 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
1949 G_N_ELEMENTS (drag_types_dest_contact));
1950 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1951 G_N_ELEMENTS (drag_types_dest_file));
1953 /* Set up smiley menu */
1954 smiley_manager = empathy_smiley_manager_dup_singleton ();
1955 submenu = empathy_smiley_menu_new (smiley_manager,
1956 chat_window_insert_smiley_activate_cb,
1958 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1959 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
1960 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
1961 g_object_unref (smiley_manager);
1963 /* Set up signals we can't do with ui file since we may need to
1964 * block/unblock them at some later stage.
1967 g_signal_connect (priv->dialog,
1969 G_CALLBACK (chat_window_delete_event_cb),
1971 g_signal_connect (priv->dialog,
1973 G_CALLBACK (chat_window_focus_in_event_cb),
1975 g_signal_connect_after (priv->notebook,
1977 G_CALLBACK (chat_window_page_switched_cb),
1979 g_signal_connect (priv->notebook,
1981 G_CALLBACK (chat_window_page_added_cb),
1983 g_signal_connect (priv->notebook,
1985 G_CALLBACK (chat_window_page_removed_cb),
1988 /* Set up drag and drop */
1989 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
1990 GTK_DEST_DEFAULT_HIGHLIGHT,
1992 G_N_ELEMENTS (drag_types_dest),
1993 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1995 /* connect_after to allow GtkNotebook's built-in tab switching */
1996 g_signal_connect_after (priv->notebook,
1998 G_CALLBACK (chat_window_drag_motion),
2000 g_signal_connect (priv->notebook,
2001 "drag-data-received",
2002 G_CALLBACK (chat_window_drag_data_received),
2004 g_signal_connect (priv->notebook,
2006 G_CALLBACK (chat_window_drag_drop),
2009 chat_windows = g_list_prepend (chat_windows, window);
2011 /* Set up private details */
2013 priv->chats_new_msg = NULL;
2014 priv->chats_composing = NULL;
2015 priv->current_chat = NULL;
2016 priv->notification = NULL;
2018 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2020 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2021 priv->chat_manager_chats_changed_id =
2022 g_signal_connect (priv->chat_manager, "chats-changed",
2023 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2026 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2027 empathy_chat_manager_get_num_chats (priv->chat_manager),
2032 empathy_chat_window_new (void)
2034 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
2037 /* Returns the window to open a new tab in if there is only one window
2038 * visble, otherwise, returns NULL indicating that a new window should
2042 empathy_chat_window_get_default (gboolean room)
2045 gboolean separate_windows = TRUE;
2047 empathy_conf_get_bool (empathy_conf_get (),
2048 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
2051 if (separate_windows) {
2052 /* Always create a new window */
2056 for (l = chat_windows; l; l = l->next) {
2057 EmpathyChatWindowPriv *priv;
2058 EmpathyChatWindow *chat_window;
2061 chat_window = l->data;
2062 priv = GET_PRIV (chat_window);
2064 dialog = empathy_chat_window_get_dialog (chat_window);
2065 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
2066 guint nb_rooms, nb_private;
2067 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2069 /* Skip the window if there aren't any rooms in it */
2070 if (room && nb_rooms == 0)
2073 /* Skip the window if there aren't any 1-1 chats in it */
2074 if (!room && nb_private == 0)
2077 /* Found a visible window on this desktop */
2086 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2088 EmpathyChatWindowPriv *priv;
2090 g_return_val_if_fail (window != NULL, NULL);
2092 priv = GET_PRIV (window);
2094 return priv->dialog;
2098 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2101 EmpathyChatWindowPriv *priv;
2103 GtkWidget *popup_label;
2105 GValue value = { 0, };
2107 g_return_if_fail (window != NULL);
2108 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2110 priv = GET_PRIV (window);
2112 /* Reference the chat object */
2113 g_object_ref (chat);
2115 /* If this window has just been created, position it */
2116 if (priv->chats == NULL) {
2117 const gchar *name = "chat-window";
2118 gboolean separate_windows;
2120 empathy_conf_get_bool (empathy_conf_get (),
2121 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
2124 if (separate_windows) {
2125 name = empathy_chat_get_id (chat);
2127 else if (empathy_chat_is_room (chat)) {
2128 name = "room-window";
2131 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2134 child = GTK_WIDGET (chat);
2135 label = chat_window_create_label (window, chat, TRUE);
2136 popup_label = chat_window_create_label (window, chat, FALSE);
2137 gtk_widget_show (child);
2139 g_signal_connect (chat, "notify::name",
2140 G_CALLBACK (chat_window_chat_notify_cb),
2142 g_signal_connect (chat, "notify::subject",
2143 G_CALLBACK (chat_window_chat_notify_cb),
2145 g_signal_connect (chat, "notify::remote-contact",
2146 G_CALLBACK (chat_window_chat_notify_cb),
2148 chat_window_chat_notify_cb (chat);
2150 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2151 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2152 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2153 g_value_init (&value, G_TYPE_BOOLEAN);
2154 g_value_set_boolean (&value, TRUE);
2155 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2156 child, "tab-expand" , &value);
2157 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2158 child, "tab-fill" , &value);
2159 g_value_unset (&value);
2161 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2165 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2168 EmpathyChatWindowPriv *priv;
2170 EmpathyContact *remote_contact;
2171 EmpathyChatManager *chat_manager;
2173 g_return_if_fail (window != NULL);
2174 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2176 priv = GET_PRIV (window);
2178 g_signal_handlers_disconnect_by_func (chat,
2179 chat_window_chat_notify_cb,
2181 remote_contact = g_object_get_data (G_OBJECT (chat),
2182 "chat-window-remote-contact");
2183 if (remote_contact) {
2184 g_signal_handlers_disconnect_by_func (remote_contact,
2185 chat_window_update_chat_tab,
2189 chat_manager = empathy_chat_manager_dup_singleton ();
2190 empathy_chat_manager_closed_chat (chat_manager, chat);
2191 g_object_unref (chat_manager);
2193 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2195 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2197 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2199 g_object_unref (chat);
2203 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2204 EmpathyChatWindow *new_window,
2209 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2210 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2211 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2213 widget = GTK_WIDGET (chat);
2215 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2216 G_OBJECT (widget)->ref_count);
2218 /* We reference here to make sure we don't loose the widget
2219 * and the EmpathyChat object during the move.
2221 g_object_ref (chat);
2222 g_object_ref (widget);
2224 empathy_chat_window_remove_chat (old_window, chat);
2225 empathy_chat_window_add_chat (new_window, chat);
2227 g_object_unref (widget);
2228 g_object_unref (chat);
2232 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2235 EmpathyChatWindowPriv *priv;
2238 g_return_if_fail (window != NULL);
2239 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2241 priv = GET_PRIV (window);
2243 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2245 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2250 empathy_chat_window_has_focus (EmpathyChatWindow *window)
2252 EmpathyChatWindowPriv *priv;
2255 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
2257 priv = GET_PRIV (window);
2259 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
2265 empathy_chat_window_find_chat (TpAccount *account,
2270 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2272 for (l = chat_windows; l; l = l->next) {
2273 EmpathyChatWindowPriv *priv;
2274 EmpathyChatWindow *window;
2278 priv = GET_PRIV (window);
2280 for (ll = priv->chats; ll; ll = ll->next) {
2285 if (account == empathy_chat_get_account (chat) &&
2286 !tp_strdiff (id, empathy_chat_get_id (chat))) {
2296 empathy_chat_window_present_chat (EmpathyChat *chat)
2298 EmpathyChatWindow *window;
2299 EmpathyChatWindowPriv *priv;
2301 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2303 window = chat_window_find_chat (chat);
2305 /* If the chat has no window, create one */
2306 if (window == NULL) {
2307 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2309 window = empathy_chat_window_new ();
2312 empathy_chat_window_add_chat (window, chat);
2315 priv = GET_PRIV (window);
2316 empathy_chat_window_switch_to_chat (window, chat);
2317 empathy_window_present (GTK_WINDOW (priv->dialog));
2319 gtk_widget_grab_focus (chat->input_text_view);
2323 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2327 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2329 guint _nb_rooms = 0, _nb_private = 0;
2331 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2332 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2338 if (nb_rooms != NULL)
2339 *nb_rooms = _nb_rooms;
2340 if (nb_private != NULL)
2341 *nb_private = _nb_private;