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>
36 #include <glib/gi18n.h>
37 #include <libnotify/notification.h>
39 #include <telepathy-glib/telepathy-glib.h>
41 #include <libempathy/empathy-contact.h>
42 #include <libempathy/empathy-message.h>
43 #include <libempathy/empathy-chatroom-manager.h>
44 #include <libempathy/empathy-utils.h>
45 #include <libempathy/empathy-tp-contact-factory.h>
46 #include <libempathy/empathy-contact-list.h>
48 #include <libempathy-gtk/empathy-images.h>
49 #include <libempathy-gtk/empathy-conf.h>
50 #include <libempathy-gtk/empathy-contact-dialogs.h>
51 #include <libempathy-gtk/empathy-log-window.h>
52 #include <libempathy-gtk/empathy-geometry.h>
53 #include <libempathy-gtk/empathy-smiley-manager.h>
54 #include <libempathy-gtk/empathy-sound.h>
55 #include <libempathy-gtk/empathy-ui-utils.h>
56 #include <libempathy-gtk/empathy-notify-manager.h>
58 #include "empathy-chat-manager.h"
59 #include "empathy-chat-window.h"
60 #include "empathy-about-dialog.h"
61 #include "empathy-invite-participant-dialog.h"
63 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
64 #include <libempathy/empathy-debug.h>
66 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
68 #define X_EARLIER_OR_EQL(t1, t2) \
69 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
70 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
73 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
75 EmpathyChat *current_chat;
78 GList *chats_composing;
80 gboolean dnd_same_window;
81 EmpathyChatroomManager *chatroom_manager;
82 EmpathyNotifyManager *notify_mgr;
85 NotifyNotification *notification;
87 GtkTargetList *contact_targets;
88 GtkTargetList *file_targets;
90 EmpathyChatManager *chat_manager;
91 gulong chat_manager_chats_changed_id;
94 GtkUIManager *ui_manager;
95 GtkAction *menu_conv_insert_smiley;
96 GtkAction *menu_conv_favorite;
97 GtkAction *menu_conv_toggle_contacts;
99 GtkAction *menu_edit_cut;
100 GtkAction *menu_edit_copy;
101 GtkAction *menu_edit_paste;
102 GtkAction *menu_edit_find;
104 GtkAction *menu_tabs_next;
105 GtkAction *menu_tabs_prev;
106 GtkAction *menu_tabs_undo_close_tab;
107 GtkAction *menu_tabs_left;
108 GtkAction *menu_tabs_right;
109 GtkAction *menu_tabs_detach;
111 /* Last user action time we acted upon to show a tab */
112 guint32 x_user_action_time;
113 } EmpathyChatWindowPriv;
115 static GList *chat_windows = NULL;
117 static const guint tab_accel_keys[] = {
118 GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
119 GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
123 DND_DRAG_TYPE_CONTACT_ID,
124 DND_DRAG_TYPE_URI_LIST,
128 static const GtkTargetEntry drag_types_dest[] = {
129 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
130 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
131 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
132 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
135 static const GtkTargetEntry drag_types_dest_contact[] = {
136 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
139 static const GtkTargetEntry drag_types_dest_file[] = {
140 /* must be first to be prioritized, in order to receive the
141 * note's file path from Tomboy instead of an URI */
142 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
146 static void chat_window_update (EmpathyChatWindow *window);
148 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
151 chat_window_accel_cb (GtkAccelGroup *accelgroup,
155 EmpathyChatWindow *window)
157 EmpathyChatWindowPriv *priv;
161 priv = GET_PRIV (window);
163 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
164 if (tab_accel_keys[i] == key) {
171 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
175 static EmpathyChatWindow *
176 chat_window_find_chat (EmpathyChat *chat)
178 EmpathyChatWindowPriv *priv;
181 for (l = chat_windows; l; l = l->next) {
182 priv = GET_PRIV (l->data);
183 ll = g_list_find (priv->chats, chat);
193 chat_window_close_clicked_cb (GtkAction *action,
196 EmpathyChatWindow *window;
198 window = chat_window_find_chat (chat);
199 empathy_chat_window_remove_chat (window, chat);
203 chat_tab_style_set_cb (GtkWidget *hbox,
204 GtkStyle *previous_style,
208 int char_width, h, w;
209 PangoContext *context;
210 PangoFontMetrics *metrics;
212 button = g_object_get_data (G_OBJECT (user_data),
213 "chat-window-tab-close-button");
214 context = gtk_widget_get_pango_context (hbox);
216 metrics = pango_context_get_metrics (context, gtk_widget_get_style (hbox)->font_desc,
217 pango_context_get_language (context));
218 char_width = pango_font_metrics_get_approximate_char_width (metrics);
219 pango_font_metrics_unref (metrics);
221 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
222 GTK_ICON_SIZE_MENU, &w, &h);
224 /* Request at least about 12 chars width plus at least space for the status
225 * image and the close button */
226 gtk_widget_set_size_request (hbox,
227 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
229 gtk_widget_set_size_request (button, w, h);
233 chat_window_create_label (EmpathyChatWindow *window,
235 gboolean is_tab_label)
237 EmpathyChatWindowPriv *priv;
239 GtkWidget *name_label;
240 GtkWidget *status_image;
241 GtkWidget *close_button;
242 GtkWidget *close_image;
243 GtkWidget *event_box;
244 GtkWidget *event_box_hbox;
245 PangoAttrList *attr_list;
246 PangoAttribute *attr;
248 priv = GET_PRIV (window);
250 /* The spacing between the button and the label. */
251 hbox = gtk_hbox_new (FALSE, 0);
253 event_box = gtk_event_box_new ();
254 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
256 name_label = gtk_label_new (NULL);
258 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
260 attr_list = pango_attr_list_new ();
261 attr = pango_attr_scale_new (1/1.2);
262 attr->start_index = 0;
263 attr->end_index = -1;
264 pango_attr_list_insert (attr_list, attr);
265 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
266 pango_attr_list_unref (attr_list);
268 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
269 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
270 g_object_set_data (G_OBJECT (chat),
271 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
274 status_image = gtk_image_new ();
276 /* Spacing between the icon and label. */
277 event_box_hbox = gtk_hbox_new (FALSE, 0);
279 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
280 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
282 g_object_set_data (G_OBJECT (chat),
283 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
285 g_object_set_data (G_OBJECT (chat),
286 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
289 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
290 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
293 close_button = gtk_button_new ();
294 gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
295 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
297 /* We don't want focus/keynav for the button to avoid clutter, and
298 * Ctrl-W works anyway.
300 gtk_widget_set_can_focus (close_button, FALSE);
301 gtk_widget_set_can_default (close_button, FALSE);
303 /* Set the name to make the special rc style match. */
304 gtk_widget_set_name (close_button, "empathy-close-button");
306 close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
308 gtk_container_add (GTK_CONTAINER (close_button), close_image);
310 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
312 g_signal_connect (close_button,
314 G_CALLBACK (chat_window_close_clicked_cb),
317 /* React to theme changes and also setup the size correctly. */
318 g_signal_connect (hbox,
320 G_CALLBACK (chat_tab_style_set_cb),
324 gtk_widget_show_all (hbox);
330 _submenu_notify_visible_changed_cb (GObject *object,
334 g_signal_handlers_disconnect_by_func (object,
335 _submenu_notify_visible_changed_cb,
337 chat_window_update (EMPATHY_CHAT_WINDOW (userdata));
341 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
346 gboolean wrap_around;
347 gboolean is_connected;
350 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
351 first_page = (page_num == 0);
352 last_page = (page_num == (num_pages - 1));
353 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
355 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
357 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
359 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
361 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
362 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
363 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
364 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
368 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
369 EmpathyChatWindow *self)
371 EmpathyTpChat *tp_chat;
372 TpConnection *connection;
374 gboolean sensitive = FALSE;
376 g_return_if_fail (priv->current_chat != NULL);
378 action = gtk_ui_manager_get_action (priv->ui_manager,
379 "/chats_menubar/menu_conv/menu_conv_invite_participant");
380 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
382 if (tp_chat != NULL) {
383 connection = empathy_tp_chat_get_connection (tp_chat);
385 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
386 (tp_connection_get_status (connection, NULL) ==
387 TP_CONNECTION_STATUS_CONNECTED);
390 gtk_action_set_sensitive (action, sensitive);
394 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
395 EmpathyChatWindow *window)
397 GtkWidget *menu, *submenu, *orig_submenu;
399 menu = gtk_ui_manager_get_widget (priv->ui_manager,
400 "/chats_menubar/menu_contact");
401 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
403 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
404 submenu = empathy_chat_get_contact_menu (priv->current_chat);
405 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
406 gtk_widget_show (menu);
408 tp_g_signal_connect_object (orig_submenu,
410 (GCallback)_submenu_notify_visible_changed_cb,
416 get_all_unread_messages (EmpathyChatWindowPriv *priv)
421 for (l = priv->chats_new_msg; l != NULL; l = g_list_next (l)) {
422 EmpathyChat *chat = l->data;
424 nb += empathy_chat_get_nb_unread_messages (chat);
431 get_window_title_name (EmpathyChatWindowPriv *priv)
433 const gchar *active_name;
435 guint current_unread_msgs;
437 nb_chats = g_list_length (priv->chats);
438 g_assert (nb_chats > 0);
440 active_name = empathy_chat_get_name (priv->current_chat);
442 current_unread_msgs = empathy_chat_get_nb_unread_messages (
447 if (current_unread_msgs == 0)
448 return g_strdup (active_name);
450 return g_strdup_printf (ngettext (
452 "%s (%d unread)", current_unread_msgs),
453 active_name, current_unread_msgs);
455 guint nb_others = nb_chats - 1;
456 guint all_unread_msgs;
458 all_unread_msgs = get_all_unread_messages (priv);
460 if (all_unread_msgs == 0) {
461 /* no unread message */
462 return g_strdup_printf (ngettext (
464 "%s (and %u others)", nb_others),
465 active_name, nb_others);
468 else if (all_unread_msgs == current_unread_msgs) {
469 /* unread messages are in the current tab */
470 return g_strdup_printf (ngettext (
472 "%s (%d unread)", current_unread_msgs),
473 active_name, current_unread_msgs);
476 else if (current_unread_msgs == 0) {
477 /* unread messages are in other tabs */
478 return g_strdup_printf (ngettext (
479 "%s (%d unread from others)",
480 "%s (%d unread from others)",
482 active_name, all_unread_msgs);
486 /* unread messages are in all the tabs */
487 return g_strdup_printf (ngettext (
488 "%s (%d unread from all)",
489 "%s (%d unread from all)",
491 active_name, all_unread_msgs);
497 chat_window_title_update (EmpathyChatWindowPriv *priv)
501 name = get_window_title_name (priv);
502 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
507 chat_window_icon_update (EmpathyChatWindowPriv *priv)
510 EmpathyContact *remote_contact;
511 gboolean avatar_in_icon;
514 n_chats = g_list_length (priv->chats);
516 /* Update window icon */
517 if (priv->chats_new_msg) {
518 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
519 EMPATHY_IMAGE_MESSAGE);
521 empathy_conf_get_bool (empathy_conf_get (),
522 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON,
525 if (n_chats == 1 && avatar_in_icon) {
526 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
527 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
528 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
531 g_object_unref (icon);
534 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
540 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
544 GtkWidget *chat_close_button;
547 if (num_pages == 1) {
548 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
549 chat_close_button = g_object_get_data (G_OBJECT (chat),
550 "chat-window-tab-close-button");
551 gtk_widget_hide (chat_close_button);
553 for (i=0; i<num_pages; i++) {
554 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
555 chat_close_button = g_object_get_data (G_OBJECT (chat),
556 "chat-window-tab-close-button");
557 gtk_widget_show (chat_close_button);
563 chat_window_update (EmpathyChatWindow *window)
565 EmpathyChatWindowPriv *priv = GET_PRIV (window);
568 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
570 /* Update Tab menu */
571 chat_window_menu_context_update (priv,
574 chat_window_conversation_menu_update (priv, window);
576 chat_window_contact_menu_update (priv,
579 chat_window_title_update (priv);
581 chat_window_icon_update (priv);
583 chat_window_close_button_update (priv,
588 append_markup_printf (GString *string,
595 va_start (args, format);
597 tmp = g_markup_vprintf_escaped (format, args);
598 g_string_append (string, tmp);
605 chat_window_update_chat_tab (EmpathyChat *chat)
607 EmpathyChatWindow *window;
608 EmpathyChatWindowPriv *priv;
609 EmpathyContact *remote_contact;
613 const gchar *subject;
614 const gchar *status = NULL;
618 const gchar *icon_name;
619 GtkWidget *tab_image;
620 GtkWidget *menu_image;
622 window = chat_window_find_chat (chat);
626 priv = GET_PRIV (window);
628 /* Get information */
629 name = empathy_chat_get_name (chat);
630 account = empathy_chat_get_account (chat);
631 subject = empathy_chat_get_subject (chat);
632 remote_contact = empathy_chat_get_remote_contact (chat);
634 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
635 name, tp_proxy_get_object_path (account), subject, remote_contact);
637 /* Update tab image */
638 if (empathy_chat_get_tp_chat (chat) == NULL) {
639 /* No TpChat, we are disconnected */
642 else if (g_list_find (priv->chats_new_msg, chat)) {
643 icon_name = EMPATHY_IMAGE_MESSAGE;
645 else if (g_list_find (priv->chats_composing, chat)) {
646 icon_name = EMPATHY_IMAGE_TYPING;
648 else if (remote_contact) {
649 icon_name = empathy_icon_name_for_contact (remote_contact);
651 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
654 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
655 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
656 if (icon_name != NULL) {
657 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
658 gtk_widget_show (tab_image);
659 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
660 gtk_widget_show (menu_image);
662 gtk_widget_hide (tab_image);
663 gtk_widget_hide (menu_image);
666 /* Update tab tooltip */
667 tooltip = g_string_new (NULL);
669 if (remote_contact) {
670 id = empathy_contact_get_id (remote_contact);
671 status = empathy_contact_get_presence_message (remote_contact);
676 append_markup_printf (tooltip,
677 "<b>%s</b><small> (%s)</small>",
679 tp_account_get_display_name (account));
681 if (!EMP_STR_EMPTY (status)) {
682 append_markup_printf (tooltip, "\n<i>%s</i>", status);
686 append_markup_printf (tooltip, "\n<b>%s</b> %s",
687 _("Topic:"), subject);
690 if (g_list_find (priv->chats_composing, chat)) {
691 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
694 markup = g_string_free (tooltip, FALSE);
695 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
696 gtk_widget_set_tooltip_markup (widget, markup);
697 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
698 gtk_widget_set_tooltip_markup (widget, markup);
701 /* Update tab and menu label */
702 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
703 gtk_label_set_text (GTK_LABEL (widget), name);
704 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
705 gtk_label_set_text (GTK_LABEL (widget), name);
707 /* Update the window if it's the current chat */
708 if (priv->current_chat == chat) {
709 chat_window_update (window);
714 chat_window_chat_notify_cb (EmpathyChat *chat)
716 EmpathyContact *old_remote_contact;
717 EmpathyContact *remote_contact = NULL;
719 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
720 remote_contact = empathy_chat_get_remote_contact (chat);
722 if (old_remote_contact != remote_contact) {
723 /* The remote-contact associated with the chat changed, we need
724 * to keep track of any change of that contact and update the
725 * window each time. */
726 if (remote_contact) {
727 g_signal_connect_swapped (remote_contact, "notify",
728 G_CALLBACK (chat_window_update_chat_tab),
731 if (old_remote_contact) {
732 g_signal_handlers_disconnect_by_func (old_remote_contact,
733 chat_window_update_chat_tab,
737 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
738 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
741 chat_window_update_chat_tab (chat);
745 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
746 EmpathySmiley *smiley,
749 EmpathyChatWindowPriv *priv = GET_PRIV (window);
751 GtkTextBuffer *buffer;
754 chat = priv->current_chat;
756 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
757 gtk_text_buffer_get_end_iter (buffer, &iter);
758 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
762 chat_window_conv_activate_cb (GtkAction *action,
763 EmpathyChatWindow *window)
765 EmpathyChatWindowPriv *priv = GET_PRIV (window);
768 EmpathyContact *remote_contact = NULL;
770 /* Favorite room menu */
771 is_room = empathy_chat_is_room (priv->current_chat);
775 gboolean found = FALSE;
776 EmpathyChatroom *chatroom;
778 room = empathy_chat_get_id (priv->current_chat);
779 account = empathy_chat_get_account (priv->current_chat);
780 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
782 if (chatroom != NULL)
783 found = empathy_chatroom_is_favorite (chatroom);
785 DEBUG ("This room %s favorite", found ? "is" : "is not");
786 gtk_toggle_action_set_active (
787 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
789 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
791 /* Show contacts menu */
792 g_object_get (priv->current_chat,
793 "remote-contact", &remote_contact,
794 "show-contacts", &active,
796 if (remote_contact == NULL) {
797 gtk_toggle_action_set_active (
798 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
801 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
802 (remote_contact == NULL));
803 if (remote_contact != NULL) {
804 g_object_unref (remote_contact);
809 chat_window_clear_activate_cb (GtkAction *action,
810 EmpathyChatWindow *window)
812 EmpathyChatWindowPriv *priv = GET_PRIV (window);
814 empathy_chat_clear (priv->current_chat);
818 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
819 EmpathyChatWindow *window)
821 EmpathyChatWindowPriv *priv = GET_PRIV (window);
825 EmpathyChatroom *chatroom;
827 active = gtk_toggle_action_get_active (toggle_action);
828 account = empathy_chat_get_account (priv->current_chat);
829 room = empathy_chat_get_id (priv->current_chat);
831 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
834 if (chatroom == NULL) {
837 name = empathy_chat_get_name (priv->current_chat);
838 chatroom = empathy_chatroom_new_full (account, room, name, FALSE);
839 empathy_chatroom_manager_add (priv->chatroom_manager, chatroom);
840 g_object_unref (chatroom);
843 empathy_chatroom_set_favorite (chatroom, active);
847 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
848 EmpathyChatWindow *window)
850 EmpathyChatWindowPriv *priv = GET_PRIV (window);
853 active = gtk_toggle_action_get_active (toggle_action);
855 empathy_chat_set_show_contacts (priv->current_chat, active);
859 got_contact_cb (EmpathyTpContactFactory *factory,
860 EmpathyContact *contact,
865 EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data);
868 DEBUG ("Failed: %s", error->message);
871 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
872 contact, _("Inviting you to this room"));
877 chat_window_invite_participant_activate_cb (GtkAction *action,
878 EmpathyChatWindow *window)
880 EmpathyChatWindowPriv *priv;
882 EmpathyTpChat *tp_chat;
887 priv = GET_PRIV (window);
889 g_return_if_fail (priv->current_chat != NULL);
891 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
892 channel = empathy_tp_chat_get_channel (tp_chat);
893 account = empathy_chat_get_account (priv->current_chat);
895 dialog = empathy_invite_participant_dialog_new (
896 GTK_WINDOW (priv->dialog), account);
897 gtk_widget_show (dialog);
899 response = gtk_dialog_run (GTK_DIALOG (dialog));
901 if (response == GTK_RESPONSE_ACCEPT) {
902 TpConnection *connection;
903 EmpathyTpContactFactory *factory;
906 id = empathy_contact_selector_dialog_get_selected (
907 EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL);
908 if (EMP_STR_EMPTY (id)) goto out;
910 connection = tp_channel_borrow_connection (channel);
911 factory = empathy_tp_contact_factory_dup_singleton (connection);
913 empathy_tp_contact_factory_get_from_id (factory, id,
914 got_contact_cb, tp_chat, NULL, NULL);
916 g_object_unref (factory);
920 gtk_widget_destroy (dialog);
924 chat_window_close_activate_cb (GtkAction *action,
925 EmpathyChatWindow *window)
927 EmpathyChatWindowPriv *priv;
929 priv = GET_PRIV (window);
931 g_return_if_fail (priv->current_chat != NULL);
933 empathy_chat_window_remove_chat (window, priv->current_chat);
937 chat_window_edit_activate_cb (GtkAction *action,
938 EmpathyChatWindow *window)
940 EmpathyChatWindowPriv *priv;
941 GtkClipboard *clipboard;
942 GtkTextBuffer *buffer;
943 gboolean text_available;
945 priv = GET_PRIV (window);
947 g_return_if_fail (priv->current_chat != NULL);
949 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
950 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
951 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
952 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
956 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
957 if (gtk_text_buffer_get_has_selection (buffer)) {
958 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
959 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
963 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
965 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
966 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
969 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
970 text_available = gtk_clipboard_wait_is_text_available (clipboard);
971 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
975 chat_window_cut_activate_cb (GtkAction *action,
976 EmpathyChatWindow *window)
978 EmpathyChatWindowPriv *priv;
980 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
982 priv = GET_PRIV (window);
984 empathy_chat_cut (priv->current_chat);
988 chat_window_copy_activate_cb (GtkAction *action,
989 EmpathyChatWindow *window)
991 EmpathyChatWindowPriv *priv;
993 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
995 priv = GET_PRIV (window);
997 empathy_chat_copy (priv->current_chat);
1001 chat_window_paste_activate_cb (GtkAction *action,
1002 EmpathyChatWindow *window)
1004 EmpathyChatWindowPriv *priv;
1006 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1008 priv = GET_PRIV (window);
1010 empathy_chat_paste (priv->current_chat);
1014 chat_window_find_activate_cb (GtkAction *action,
1015 EmpathyChatWindow *window)
1017 EmpathyChatWindowPriv *priv;
1019 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1021 priv = GET_PRIV (window);
1023 empathy_chat_find (priv->current_chat);
1027 chat_window_tabs_next_activate_cb (GtkAction *action,
1028 EmpathyChatWindow *window)
1030 EmpathyChatWindowPriv *priv;
1032 gint index_, numPages;
1033 gboolean wrap_around;
1035 priv = GET_PRIV (window);
1037 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1038 &wrap_around, NULL);
1040 chat = priv->current_chat;
1041 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1042 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1044 if (index_ == (numPages - 1) && wrap_around) {
1045 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1049 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1053 chat_window_tabs_previous_activate_cb (GtkAction *action,
1054 EmpathyChatWindow *window)
1056 EmpathyChatWindowPriv *priv;
1058 gint index_, numPages;
1059 gboolean wrap_around;
1061 priv = GET_PRIV (window);
1063 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1064 &wrap_around, NULL);
1066 chat = priv->current_chat;
1067 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1068 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1070 if (index_ <= 0 && wrap_around) {
1071 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1075 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1079 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1080 EmpathyChatWindow *window)
1082 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1083 empathy_chat_manager_undo_closed_chat (priv->chat_manager);
1087 chat_window_tabs_left_activate_cb (GtkAction *action,
1088 EmpathyChatWindow *window)
1090 EmpathyChatWindowPriv *priv;
1092 gint index_, num_pages;
1094 priv = GET_PRIV (window);
1096 chat = priv->current_chat;
1097 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1102 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1106 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1107 chat_window_menu_context_update (priv, num_pages);
1111 chat_window_tabs_right_activate_cb (GtkAction *action,
1112 EmpathyChatWindow *window)
1114 EmpathyChatWindowPriv *priv;
1116 gint index_, num_pages;
1118 priv = GET_PRIV (window);
1120 chat = priv->current_chat;
1121 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1123 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1127 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1128 chat_window_menu_context_update (priv, num_pages);
1132 chat_window_detach_activate_cb (GtkAction *action,
1133 EmpathyChatWindow *window)
1135 EmpathyChatWindowPriv *priv;
1136 EmpathyChatWindow *new_window;
1139 priv = GET_PRIV (window);
1141 chat = priv->current_chat;
1142 new_window = empathy_chat_window_new ();
1144 empathy_chat_window_move_chat (window, new_window, chat);
1146 priv = GET_PRIV (new_window);
1147 gtk_widget_show (priv->dialog);
1151 chat_window_help_contents_activate_cb (GtkAction *action,
1152 EmpathyChatWindow *window)
1154 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1156 empathy_url_show (priv->dialog, "ghelp:empathy");
1160 chat_window_help_about_activate_cb (GtkAction *action,
1161 EmpathyChatWindow *window)
1163 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1165 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1169 chat_window_delete_event_cb (GtkWidget *dialog,
1171 EmpathyChatWindow *window)
1173 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1175 DEBUG ("Delete event received");
1177 g_object_ref (window);
1178 while (priv->chats) {
1179 empathy_chat_window_remove_chat (window, priv->chats->data);
1181 g_object_unref (window);
1187 chat_window_composing_cb (EmpathyChat *chat,
1188 gboolean is_composing,
1189 EmpathyChatWindow *window)
1191 EmpathyChatWindowPriv *priv;
1193 priv = GET_PRIV (window);
1195 if (is_composing && !g_list_find (priv->chats_composing, chat)) {
1196 priv->chats_composing = g_list_prepend (priv->chats_composing, chat);
1198 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1201 chat_window_update_chat_tab (chat);
1205 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1208 EmpathyChatWindowPriv *priv;
1210 priv = GET_PRIV (window);
1212 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1216 chat_window_notification_closed_cb (NotifyNotification *notify,
1217 EmpathyChatWindow *self)
1219 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1221 g_object_unref (notify);
1222 if (priv->notification == notify) {
1223 priv->notification = NULL;
1228 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1229 EmpathyMessage *message,
1232 EmpathyContact *sender;
1233 const gchar *header;
1237 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1238 gboolean res, has_x_canonical_append;
1239 NotifyNotification *notification = priv->notification;
1241 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1244 empathy_conf_get_bool (empathy_conf_get (),
1245 EMPATHY_PREFS_NOTIFICATIONS_FOCUS, &res);
1251 sender = empathy_message_get_sender (message);
1252 header = empathy_contact_get_name (sender);
1253 body = empathy_message_get_body (message);
1254 escaped = g_markup_escape_text (body, -1);
1255 has_x_canonical_append = empathy_notify_manager_has_capability (
1256 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1258 if (notification != NULL && !has_x_canonical_append) {
1259 /* if the notification server supports x-canonical-append, it is
1260 better to not use notify_notification_update to avoid
1261 overwriting the current notification message */
1262 notify_notification_update (notification,
1263 header, escaped, NULL);
1265 /* if the notification server supports x-canonical-append,
1266 the hint will be added, so that the message from the
1267 just created notification will be automatically appended
1268 to an existing notification with the same title.
1269 In this way the previous message will not be lost: the new
1270 message will appear below it, in the same notification */
1271 notification = notify_notification_new (header, escaped, NULL, NULL);
1273 if (priv->notification == NULL) {
1274 priv->notification = notification;
1277 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1279 g_signal_connect (notification, "closed",
1280 G_CALLBACK (chat_window_notification_closed_cb), window);
1282 if (has_x_canonical_append) {
1283 notify_notification_set_hint_string (notification,
1284 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
1288 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1289 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1291 if (pixbuf != NULL) {
1292 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1293 g_object_unref (pixbuf);
1296 notify_notification_show (notification, NULL);
1302 chat_window_set_highlight_room_tab_label (EmpathyChat *chat)
1307 if (!empathy_chat_is_room (chat))
1310 markup = g_markup_printf_escaped (
1311 "<span color=\"red\" weight=\"bold\">%s</span>",
1312 empathy_chat_get_name (chat));
1314 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1315 gtk_label_set_markup (GTK_LABEL (widget), markup);
1320 chat_window_new_message_cb (EmpathyChat *chat,
1321 EmpathyMessage *message,
1322 EmpathyChatWindow *window)
1324 EmpathyChatWindowPriv *priv;
1326 gboolean needs_urgency;
1327 EmpathyContact *sender;
1329 priv = GET_PRIV (window);
1331 has_focus = empathy_chat_window_has_focus (window);
1333 /* - if we're the sender, we play the sound if it's specified in the
1334 * preferences and we're not away.
1335 * - if we receive a message, we play the sound if it's specified in the
1336 * preferences and the window does not have focus on the chat receiving
1340 sender = empathy_message_get_sender (message);
1342 if (empathy_contact_is_user (sender)) {
1343 empathy_sound_play (GTK_WIDGET (priv->dialog),
1344 EMPATHY_SOUND_MESSAGE_OUTGOING);
1347 if (has_focus && priv->current_chat == chat) {
1348 /* window and tab are focused so consider the message to be read */
1350 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1351 empathy_chat_messages_read (chat);
1355 if (!g_list_find (priv->chats_new_msg, chat)) {
1356 priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
1357 chat_window_update_chat_tab (chat);
1360 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1361 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1362 * an unamed MUC (msn-like).
1363 * In case of a MUC, we set urgency only if the message contains our
1365 if (empathy_chat_is_room (chat) ||
1366 empathy_chat_get_remote_contact (chat) == NULL) {
1367 needs_urgency = empathy_message_should_highlight (message);
1369 needs_urgency = TRUE;
1372 if (needs_urgency) {
1374 chat_window_set_urgency_hint (window, TRUE);
1375 chat_window_set_highlight_room_tab_label (chat);
1378 empathy_sound_play (GTK_WIDGET (priv->dialog),
1379 EMPATHY_SOUND_MESSAGE_INCOMING);
1380 chat_window_show_or_update_notification (window, message, chat);
1383 /* update the number of unread messages */
1384 chat_window_title_update (priv);
1387 static GtkNotebook *
1388 chat_window_detach_hook (GtkNotebook *source,
1394 EmpathyChatWindowPriv *priv;
1395 EmpathyChatWindow *window, *new_window;
1398 chat = EMPATHY_CHAT (page);
1399 window = chat_window_find_chat (chat);
1401 new_window = empathy_chat_window_new ();
1402 priv = GET_PRIV (new_window);
1404 DEBUG ("Detach hook called");
1406 empathy_chat_window_move_chat (window, new_window, chat);
1408 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1409 gtk_widget_show (priv->dialog);
1415 chat_window_page_switched_cb (GtkNotebook *notebook,
1416 GtkNotebookPage *page,
1418 EmpathyChatWindow *window)
1420 EmpathyChatWindowPriv *priv;
1424 DEBUG ("Page switched");
1426 priv = GET_PRIV (window);
1428 child = gtk_notebook_get_nth_page (notebook, page_num);
1429 chat = EMPATHY_CHAT (child);
1431 if (priv->page_added) {
1432 priv->page_added = FALSE;
1433 empathy_chat_scroll_down (chat);
1435 else if (priv->current_chat == chat) {
1439 priv->current_chat = chat;
1440 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1441 empathy_chat_messages_read (chat);
1443 chat_window_update_chat_tab (chat);
1447 chat_window_page_added_cb (GtkNotebook *notebook,
1450 EmpathyChatWindow *window)
1452 EmpathyChatWindowPriv *priv;
1455 priv = GET_PRIV (window);
1457 /* If we just received DND to the same window, we don't want
1458 * to do anything here like removing the tab and then readding
1459 * it, so we return here and in "page-added".
1461 if (priv->dnd_same_window) {
1462 DEBUG ("Page added (back to the same window)");
1463 priv->dnd_same_window = FALSE;
1467 DEBUG ("Page added");
1469 /* Get chat object */
1470 chat = EMPATHY_CHAT (child);
1472 /* Connect chat signals for this window */
1473 g_signal_connect (chat, "composing",
1474 G_CALLBACK (chat_window_composing_cb),
1476 g_signal_connect (chat, "new-message",
1477 G_CALLBACK (chat_window_new_message_cb),
1479 g_signal_connect (chat, "notify::tp-chat",
1480 G_CALLBACK (chat_window_update_chat_tab),
1483 /* Set flag so we know to perform some special operations on
1484 * switch page due to the new page being added.
1486 priv->page_added = TRUE;
1488 /* Get list of chats up to date */
1489 priv->chats = g_list_append (priv->chats, chat);
1491 chat_window_update_chat_tab (chat);
1495 chat_window_page_removed_cb (GtkNotebook *notebook,
1498 EmpathyChatWindow *window)
1500 EmpathyChatWindowPriv *priv;
1503 priv = GET_PRIV (window);
1505 /* If we just received DND to the same window, we don't want
1506 * to do anything here like removing the tab and then readding
1507 * it, so we return here and in "page-added".
1509 if (priv->dnd_same_window) {
1510 DEBUG ("Page removed (and will be readded to same window)");
1514 DEBUG ("Page removed");
1516 /* Get chat object */
1517 chat = EMPATHY_CHAT (child);
1519 /* Disconnect all signal handlers for this chat and this window */
1520 g_signal_handlers_disconnect_by_func (chat,
1521 G_CALLBACK (chat_window_composing_cb),
1523 g_signal_handlers_disconnect_by_func (chat,
1524 G_CALLBACK (chat_window_new_message_cb),
1526 g_signal_handlers_disconnect_by_func (chat,
1527 G_CALLBACK (chat_window_update_chat_tab),
1530 /* Keep list of chats up to date */
1531 priv->chats = g_list_remove (priv->chats, chat);
1532 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1533 empathy_chat_messages_read (chat);
1534 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1536 if (priv->chats == NULL) {
1537 g_object_unref (window);
1539 chat_window_update (window);
1544 chat_window_focus_in_event_cb (GtkWidget *widget,
1546 EmpathyChatWindow *window)
1548 EmpathyChatWindowPriv *priv;
1550 priv = GET_PRIV (window);
1552 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
1553 empathy_chat_messages_read (priv->current_chat);
1555 chat_window_set_urgency_hint (window, FALSE);
1557 /* Update the title, since we now mark all unread messages as read. */
1558 chat_window_update_chat_tab (priv->current_chat);
1564 chat_window_drag_drop (GtkWidget *widget,
1565 GdkDragContext *context,
1569 EmpathyChatWindow *window)
1572 EmpathyChatWindowPriv *priv;
1574 priv = GET_PRIV (window);
1576 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1577 if (target == GDK_NONE)
1578 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1580 if (target != GDK_NONE) {
1581 gtk_drag_get_data (widget, context, target, time_);
1589 chat_window_drag_motion (GtkWidget *widget,
1590 GdkDragContext *context,
1594 EmpathyChatWindow *window)
1597 EmpathyChatWindowPriv *priv;
1599 priv = GET_PRIV (window);
1601 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1602 if (target != GDK_NONE) {
1603 /* This is a file drag. Ensure the contact is online and set the
1604 drag type to COPY. Note that it's possible that the tab will
1605 be switched by GTK+ after a timeout from drag_motion without
1606 getting another drag_motion to disable the drop. You have
1607 to hold your mouse really still.
1609 EmpathyContact *contact;
1611 priv = GET_PRIV (window);
1612 contact = empathy_chat_get_remote_contact (priv->current_chat);
1613 /* contact is NULL for multi-user chats. We don't do
1614 * file transfers to MUCs. We also don't send files
1615 * to offline contacts or contacts that don't support
1618 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1619 gdk_drag_status (context, 0, time_);
1622 if (!(empathy_contact_get_capabilities (contact)
1623 & EMPATHY_CAPABILITIES_FT)) {
1624 gdk_drag_status (context, 0, time_);
1627 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1631 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1632 if (target != GDK_NONE) {
1633 /* This is a drag of a contact from a contact list. Set to COPY.
1634 FIXME: If this drag is to a MUC window, it invites the user.
1635 Otherwise, it opens a chat. Should we use a different drag
1636 type for invites? Should we allow ASK?
1638 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1646 chat_window_drag_data_received (GtkWidget *widget,
1647 GdkDragContext *context,
1650 GtkSelectionData *selection,
1653 EmpathyChatWindow *window)
1655 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1656 EmpathyChat *chat = NULL;
1657 EmpathyChatWindow *old_window;
1658 TpAccount *account = NULL;
1659 TpAccountManager *account_manager;
1662 const gchar *account_id;
1663 const gchar *contact_id;
1665 id = (const gchar*) gtk_selection_data_get_data (selection);
1667 /* FIXME: Perhaps should be sure that the account manager is
1668 * prepared before calling _ensure_account on it. */
1669 account_manager = tp_account_manager_dup ();
1671 DEBUG ("DND contact from roster with id:'%s'", id);
1673 strv = g_strsplit (id, ":", 2);
1674 if (g_strv_length (strv) == 2) {
1675 account_id = strv[0];
1676 contact_id = strv[1];
1678 tp_account_manager_ensure_account (account_manager, account_id);
1679 if (account != NULL)
1680 chat = empathy_chat_window_find_chat (account, contact_id);
1683 if (account == NULL) {
1685 gtk_drag_finish (context, FALSE, FALSE, time_);
1690 TpConnection *connection;
1692 connection = tp_account_get_connection (account);
1695 empathy_dispatcher_chat_with_contact_id (
1696 connection, contact_id, gtk_get_current_event_time (), NULL, NULL);
1702 g_object_unref (account_manager);
1705 old_window = chat_window_find_chat (chat);
1707 if (old_window == window) {
1708 gtk_drag_finish (context, TRUE, FALSE, time_);
1712 empathy_chat_window_move_chat (old_window, window, chat);
1714 empathy_chat_window_add_chat (window, chat);
1717 /* Added to take care of any outstanding chat events */
1718 empathy_chat_window_present_chat (chat,
1719 EMPATHY_DISPATCHER_NON_USER_ACTION);
1721 /* We should return TRUE to remove the data when doing
1722 * GDK_ACTION_MOVE, but we don't here otherwise it has
1723 * weird consequences, and we handle that internally
1724 * anyway with add_chat () and remove_chat ().
1726 gtk_drag_finish (context, TRUE, FALSE, time_);
1728 else if (info == DND_DRAG_TYPE_URI_LIST) {
1729 EmpathyChatWindowPriv *priv;
1730 EmpathyContact *contact;
1733 priv = GET_PRIV (window);
1734 contact = empathy_chat_get_remote_contact (priv->current_chat);
1736 /* contact is NULL when current_chat is a multi-user chat.
1737 * We don't do file transfers to MUCs, so just cancel the drag.
1739 if (contact == NULL) {
1740 gtk_drag_finish (context, TRUE, FALSE, time_);
1744 data = (const gchar *) gtk_selection_data_get_data (selection);
1745 empathy_send_file_from_uri_list (contact, data);
1747 gtk_drag_finish (context, TRUE, FALSE, time_);
1749 else if (info == DND_DRAG_TYPE_TAB) {
1751 EmpathyChatWindow *old_window = NULL;
1755 chat = (void *) gtk_selection_data_get_data (selection);
1756 old_window = chat_window_find_chat (*chat);
1759 EmpathyChatWindowPriv *priv;
1761 priv = GET_PRIV (window);
1762 priv->dnd_same_window = (old_window == window);
1763 DEBUG ("DND tab (within same window: %s)",
1764 priv->dnd_same_window ? "Yes" : "No");
1767 DEBUG ("DND from unknown source");
1768 gtk_drag_finish (context, FALSE, FALSE, time_);
1773 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1774 guint num_chats_in_manager,
1775 EmpathyChatWindow *window)
1777 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1779 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1780 num_chats_in_manager > 0);
1784 chat_window_finalize (GObject *object)
1786 EmpathyChatWindow *window;
1787 EmpathyChatWindowPriv *priv;
1789 window = EMPATHY_CHAT_WINDOW (object);
1790 priv = GET_PRIV (window);
1792 DEBUG ("Finalized: %p", object);
1794 g_object_unref (priv->ui_manager);
1795 g_object_unref (priv->chatroom_manager);
1796 g_object_unref (priv->notify_mgr);
1798 if (priv->notification != NULL) {
1799 notify_notification_close (priv->notification, NULL);
1800 priv->notification = NULL;
1803 if (priv->contact_targets) {
1804 gtk_target_list_unref (priv->contact_targets);
1806 if (priv->file_targets) {
1807 gtk_target_list_unref (priv->file_targets);
1810 if (priv->chat_manager) {
1811 g_signal_handler_disconnect (priv->chat_manager,
1812 priv->chat_manager_chats_changed_id);
1813 g_object_unref (priv->chat_manager);
1814 priv->chat_manager = NULL;
1817 chat_windows = g_list_remove (chat_windows, window);
1818 gtk_widget_destroy (priv->dialog);
1820 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1824 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1826 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1828 object_class->finalize = chat_window_finalize;
1830 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1832 /* Set up a style for the close button with no focus padding. */
1833 gtk_rc_parse_string (
1834 "style \"empathy-close-button-style\"\n"
1836 " GtkWidget::focus-padding = 0\n"
1840 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1842 gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1846 empathy_chat_window_init (EmpathyChatWindow *window)
1849 GtkAccelGroup *accel_group;
1854 GtkWidget *chat_vbox;
1856 EmpathySmileyManager *smiley_manager;
1857 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1858 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1860 window->priv = priv;
1861 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1862 gui = empathy_builder_get_file (filename,
1863 "chat_window", &priv->dialog,
1864 "chat_vbox", &chat_vbox,
1865 "ui_manager", &priv->ui_manager,
1866 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1867 "menu_conv_favorite", &priv->menu_conv_favorite,
1868 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1869 "menu_edit_cut", &priv->menu_edit_cut,
1870 "menu_edit_copy", &priv->menu_edit_copy,
1871 "menu_edit_paste", &priv->menu_edit_paste,
1872 "menu_edit_find", &priv->menu_edit_find,
1873 "menu_tabs_next", &priv->menu_tabs_next,
1874 "menu_tabs_prev", &priv->menu_tabs_prev,
1875 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1876 "menu_tabs_left", &priv->menu_tabs_left,
1877 "menu_tabs_right", &priv->menu_tabs_right,
1878 "menu_tabs_detach", &priv->menu_tabs_detach,
1882 empathy_builder_connect (gui, window,
1883 "menu_conv", "activate", chat_window_conv_activate_cb,
1884 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1885 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1886 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1887 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1888 "menu_conv_close", "activate", chat_window_close_activate_cb,
1889 "menu_edit", "activate", chat_window_edit_activate_cb,
1890 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1891 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1892 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1893 "menu_edit_find", "activate", chat_window_find_activate_cb,
1894 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1895 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1896 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1897 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1898 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1899 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1900 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1901 "menu_help_about", "activate", chat_window_help_about_activate_cb,
1904 g_object_ref (priv->ui_manager);
1905 g_object_unref (gui);
1907 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1909 priv->notebook = gtk_notebook_new ();
1910 gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
1911 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
1912 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
1913 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1914 gtk_widget_show (priv->notebook);
1917 accel_group = gtk_accel_group_new ();
1918 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
1920 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
1921 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
1924 gtk_accel_group_connect (accel_group,
1931 g_object_unref (accel_group);
1933 /* Set up drag target lists */
1934 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
1935 G_N_ELEMENTS (drag_types_dest_contact));
1936 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1937 G_N_ELEMENTS (drag_types_dest_file));
1939 /* Set up smiley menu */
1940 smiley_manager = empathy_smiley_manager_dup_singleton ();
1941 submenu = empathy_smiley_menu_new (smiley_manager,
1942 chat_window_insert_smiley_activate_cb,
1944 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1945 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
1946 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
1947 g_object_unref (smiley_manager);
1949 /* Set up signals we can't do with ui file since we may need to
1950 * block/unblock them at some later stage.
1953 g_signal_connect (priv->dialog,
1955 G_CALLBACK (chat_window_delete_event_cb),
1957 g_signal_connect (priv->dialog,
1959 G_CALLBACK (chat_window_focus_in_event_cb),
1961 g_signal_connect_after (priv->notebook,
1963 G_CALLBACK (chat_window_page_switched_cb),
1965 g_signal_connect (priv->notebook,
1967 G_CALLBACK (chat_window_page_added_cb),
1969 g_signal_connect (priv->notebook,
1971 G_CALLBACK (chat_window_page_removed_cb),
1974 /* Set up drag and drop */
1975 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
1976 GTK_DEST_DEFAULT_HIGHLIGHT,
1978 G_N_ELEMENTS (drag_types_dest),
1979 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1981 /* connect_after to allow GtkNotebook's built-in tab switching */
1982 g_signal_connect_after (priv->notebook,
1984 G_CALLBACK (chat_window_drag_motion),
1986 g_signal_connect (priv->notebook,
1987 "drag-data-received",
1988 G_CALLBACK (chat_window_drag_data_received),
1990 g_signal_connect (priv->notebook,
1992 G_CALLBACK (chat_window_drag_drop),
1995 chat_windows = g_list_prepend (chat_windows, window);
1997 /* Set up private details */
1999 priv->chats_new_msg = NULL;
2000 priv->chats_composing = NULL;
2001 priv->current_chat = NULL;
2002 priv->notification = NULL;
2004 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2006 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2007 priv->chat_manager_chats_changed_id =
2008 g_signal_connect (priv->chat_manager, "chats-changed",
2009 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2012 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2013 empathy_chat_manager_get_num_chats (priv->chat_manager),
2018 empathy_chat_window_new (void)
2020 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
2023 /* Returns the window to open a new tab in if there is only one window
2024 * visble, otherwise, returns NULL indicating that a new window should
2028 empathy_chat_window_get_default (gboolean room)
2031 gboolean separate_windows = TRUE;
2033 empathy_conf_get_bool (empathy_conf_get (),
2034 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
2037 if (separate_windows) {
2038 /* Always create a new window */
2042 for (l = chat_windows; l; l = l->next) {
2043 EmpathyChatWindowPriv *priv;
2044 EmpathyChatWindow *chat_window;
2047 chat_window = l->data;
2048 priv = GET_PRIV (chat_window);
2050 dialog = empathy_chat_window_get_dialog (chat_window);
2051 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
2052 guint nb_rooms, nb_private;
2053 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2055 /* Skip the window if there aren't any rooms in it */
2056 if (room && nb_rooms == 0)
2059 /* Skip the window if there aren't any 1-1 chats in it */
2060 if (!room && nb_private == 0)
2063 /* Found a visible window on this desktop */
2072 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2074 EmpathyChatWindowPriv *priv;
2076 g_return_val_if_fail (window != NULL, NULL);
2078 priv = GET_PRIV (window);
2080 return priv->dialog;
2084 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2087 EmpathyChatWindowPriv *priv;
2089 GtkWidget *popup_label;
2091 GValue value = { 0, };
2093 g_return_if_fail (window != NULL);
2094 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2096 priv = GET_PRIV (window);
2098 /* Reference the chat object */
2099 g_object_ref (chat);
2101 /* If this window has just been created, position it */
2102 if (priv->chats == NULL) {
2103 const gchar *name = "chat-window";
2104 gboolean separate_windows;
2106 empathy_conf_get_bool (empathy_conf_get (),
2107 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
2110 if (separate_windows) {
2111 name = empathy_chat_get_id (chat);
2113 else if (empathy_chat_is_room (chat)) {
2114 name = "room-window";
2117 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2120 child = GTK_WIDGET (chat);
2121 label = chat_window_create_label (window, chat, TRUE);
2122 popup_label = chat_window_create_label (window, chat, FALSE);
2123 gtk_widget_show (child);
2125 g_signal_connect (chat, "notify::name",
2126 G_CALLBACK (chat_window_chat_notify_cb),
2128 g_signal_connect (chat, "notify::subject",
2129 G_CALLBACK (chat_window_chat_notify_cb),
2131 g_signal_connect (chat, "notify::remote-contact",
2132 G_CALLBACK (chat_window_chat_notify_cb),
2134 chat_window_chat_notify_cb (chat);
2136 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2137 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2138 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2139 g_value_init (&value, G_TYPE_BOOLEAN);
2140 g_value_set_boolean (&value, TRUE);
2141 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2142 child, "tab-expand" , &value);
2143 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2144 child, "tab-fill" , &value);
2145 g_value_unset (&value);
2147 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2151 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2154 EmpathyChatWindowPriv *priv;
2156 EmpathyContact *remote_contact;
2157 EmpathyChatManager *chat_manager;
2159 g_return_if_fail (window != NULL);
2160 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2162 priv = GET_PRIV (window);
2164 g_signal_handlers_disconnect_by_func (chat,
2165 chat_window_chat_notify_cb,
2167 remote_contact = g_object_get_data (G_OBJECT (chat),
2168 "chat-window-remote-contact");
2169 if (remote_contact) {
2170 g_signal_handlers_disconnect_by_func (remote_contact,
2171 chat_window_update_chat_tab,
2175 chat_manager = empathy_chat_manager_dup_singleton ();
2176 empathy_chat_manager_closed_chat (chat_manager, chat);
2177 g_object_unref (chat_manager);
2179 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2181 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2183 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2185 g_object_unref (chat);
2189 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2190 EmpathyChatWindow *new_window,
2195 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2196 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2197 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2199 widget = GTK_WIDGET (chat);
2201 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2202 G_OBJECT (widget)->ref_count);
2204 /* We reference here to make sure we don't loose the widget
2205 * and the EmpathyChat object during the move.
2207 g_object_ref (chat);
2208 g_object_ref (widget);
2210 empathy_chat_window_remove_chat (old_window, chat);
2211 empathy_chat_window_add_chat (new_window, chat);
2213 g_object_unref (widget);
2214 g_object_unref (chat);
2218 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2221 EmpathyChatWindowPriv *priv;
2224 g_return_if_fail (window != NULL);
2225 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2227 priv = GET_PRIV (window);
2229 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2231 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2236 empathy_chat_window_has_focus (EmpathyChatWindow *window)
2238 EmpathyChatWindowPriv *priv;
2241 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
2243 priv = GET_PRIV (window);
2245 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
2251 empathy_chat_window_find_chat (TpAccount *account,
2256 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2258 for (l = chat_windows; l; l = l->next) {
2259 EmpathyChatWindowPriv *priv;
2260 EmpathyChatWindow *window;
2264 priv = GET_PRIV (window);
2266 for (ll = priv->chats; ll; ll = ll->next) {
2271 if (account == empathy_chat_get_account (chat) &&
2272 !tp_strdiff (id, empathy_chat_get_id (chat))) {
2282 empathy_chat_window_present_chat (EmpathyChat *chat,
2285 EmpathyChatWindow *window;
2286 EmpathyChatWindowPriv *priv;
2287 guint32 x_timestamp;
2289 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2291 window = chat_window_find_chat (chat);
2293 /* If the chat has no window, create one */
2294 if (window == NULL) {
2295 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2297 window = empathy_chat_window_new ();
2298 gtk_widget_show_all (GET_PRIV (window)->dialog);
2301 empathy_chat_window_add_chat (window, chat);
2304 /* Don't force the window to show itself when it wasn't
2305 * an action by the user
2307 if (timestamp == EMPATHY_DISPATCHER_NON_USER_ACTION)
2310 priv = GET_PRIV (window);
2312 if (timestamp == G_MAXINT64) {
2313 x_timestamp = GDK_CURRENT_TIME;
2315 x_timestamp = CLAMP (timestamp, 0, G_MAXUINT32);
2316 /* Don't present or switch tab if the action was earlier than the
2317 * last actions X time, accounting for overflow and the first ever
2320 if (priv->x_user_action_time != 0
2321 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2324 priv->x_user_action_time = x_timestamp;
2327 empathy_chat_window_switch_to_chat (window, chat);
2328 empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2331 gtk_widget_grab_focus (chat->input_text_view);
2335 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2339 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2341 guint _nb_rooms = 0, _nb_private = 0;
2343 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2344 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2350 if (nb_rooms != NULL)
2351 *nb_rooms = _nb_rooms;
2352 if (nb_private != NULL)
2353 *nb_private = _nb_private;