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-gsettings.h>
45 #include <libempathy/empathy-utils.h>
46 #include <libempathy/empathy-tp-contact-factory.h>
47 #include <libempathy/empathy-contact-list.h>
49 #include <libempathy-gtk/empathy-images.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_always_urgent;
98 GtkAction *menu_conv_toggle_contacts;
100 GtkAction *menu_edit_cut;
101 GtkAction *menu_edit_copy;
102 GtkAction *menu_edit_paste;
103 GtkAction *menu_edit_find;
105 GtkAction *menu_tabs_next;
106 GtkAction *menu_tabs_prev;
107 GtkAction *menu_tabs_undo_close_tab;
108 GtkAction *menu_tabs_left;
109 GtkAction *menu_tabs_right;
110 GtkAction *menu_tabs_detach;
112 /* Last user action time we acted upon to show a tab */
113 guint32 x_user_action_time;
114 } EmpathyChatWindowPriv;
116 static GList *chat_windows = NULL;
118 static const guint tab_accel_keys[] = {
119 GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
120 GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
124 DND_DRAG_TYPE_CONTACT_ID,
125 DND_DRAG_TYPE_URI_LIST,
129 static const GtkTargetEntry drag_types_dest[] = {
130 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
131 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
132 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
133 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
136 static const GtkTargetEntry drag_types_dest_contact[] = {
137 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
140 static const GtkTargetEntry drag_types_dest_file[] = {
141 /* must be first to be prioritized, in order to receive the
142 * note's file path from Tomboy instead of an URI */
143 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
144 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
147 static void chat_window_update (EmpathyChatWindow *window);
149 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
152 chat_window_accel_cb (GtkAccelGroup *accelgroup,
156 EmpathyChatWindow *window)
158 EmpathyChatWindowPriv *priv;
162 priv = GET_PRIV (window);
164 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
165 if (tab_accel_keys[i] == key) {
172 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
176 static EmpathyChatWindow *
177 chat_window_find_chat (EmpathyChat *chat)
179 EmpathyChatWindowPriv *priv;
182 for (l = chat_windows; l; l = l->next) {
183 priv = GET_PRIV (l->data);
184 ll = g_list_find (priv->chats, chat);
194 chat_window_close_clicked_cb (GtkAction *action,
197 EmpathyChatWindow *window;
199 window = chat_window_find_chat (chat);
200 empathy_chat_window_remove_chat (window, chat);
204 chat_tab_style_set_cb (GtkWidget *hbox,
205 GtkStyle *previous_style,
209 int char_width, h, w;
210 PangoContext *context;
211 PangoFontMetrics *metrics;
213 button = g_object_get_data (G_OBJECT (user_data),
214 "chat-window-tab-close-button");
215 context = gtk_widget_get_pango_context (hbox);
217 metrics = pango_context_get_metrics (context, gtk_widget_get_style (hbox)->font_desc,
218 pango_context_get_language (context));
219 char_width = pango_font_metrics_get_approximate_char_width (metrics);
220 pango_font_metrics_unref (metrics);
222 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
223 GTK_ICON_SIZE_MENU, &w, &h);
225 /* Request at least about 12 chars width plus at least space for the status
226 * image and the close button */
227 gtk_widget_set_size_request (hbox,
228 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
230 gtk_widget_set_size_request (button, w, h);
234 chat_window_create_label (EmpathyChatWindow *window,
236 gboolean is_tab_label)
238 EmpathyChatWindowPriv *priv;
240 GtkWidget *name_label;
241 GtkWidget *status_image;
242 GtkWidget *close_button;
243 GtkWidget *close_image;
244 GtkWidget *event_box;
245 GtkWidget *event_box_hbox;
246 PangoAttrList *attr_list;
247 PangoAttribute *attr;
249 priv = GET_PRIV (window);
251 /* The spacing between the button and the label. */
252 hbox = gtk_hbox_new (FALSE, 0);
254 event_box = gtk_event_box_new ();
255 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
257 name_label = gtk_label_new (NULL);
259 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
261 attr_list = pango_attr_list_new ();
262 attr = pango_attr_scale_new (1/1.2);
263 attr->start_index = 0;
264 attr->end_index = -1;
265 pango_attr_list_insert (attr_list, attr);
266 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
267 pango_attr_list_unref (attr_list);
269 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
270 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
271 g_object_set_data (G_OBJECT (chat),
272 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
275 status_image = gtk_image_new ();
277 /* Spacing between the icon and label. */
278 event_box_hbox = gtk_hbox_new (FALSE, 0);
280 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
281 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
283 g_object_set_data (G_OBJECT (chat),
284 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
286 g_object_set_data (G_OBJECT (chat),
287 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
290 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
291 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
294 close_button = gtk_button_new ();
295 gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
296 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
298 /* We don't want focus/keynav for the button to avoid clutter, and
299 * Ctrl-W works anyway.
301 gtk_widget_set_can_focus (close_button, FALSE);
302 gtk_widget_set_can_default (close_button, FALSE);
304 /* Set the name to make the special rc style match. */
305 gtk_widget_set_name (close_button, "empathy-close-button");
307 close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
309 gtk_container_add (GTK_CONTAINER (close_button), close_image);
311 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
313 g_signal_connect (close_button,
315 G_CALLBACK (chat_window_close_clicked_cb),
318 /* React to theme changes and also setup the size correctly. */
319 g_signal_connect (hbox,
321 G_CALLBACK (chat_tab_style_set_cb),
325 gtk_widget_show_all (hbox);
331 _submenu_notify_visible_changed_cb (GObject *object,
335 g_signal_handlers_disconnect_by_func (object,
336 _submenu_notify_visible_changed_cb,
338 chat_window_update (EMPATHY_CHAT_WINDOW (userdata));
342 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
347 gboolean wrap_around;
348 gboolean is_connected;
351 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
352 first_page = (page_num == 0);
353 last_page = (page_num == (num_pages - 1));
354 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
356 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
358 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
360 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
362 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
363 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
364 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
365 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
369 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
370 EmpathyChatWindow *self)
372 EmpathyTpChat *tp_chat;
373 TpConnection *connection;
375 gboolean sensitive = FALSE;
377 g_return_if_fail (priv->current_chat != NULL);
379 action = gtk_ui_manager_get_action (priv->ui_manager,
380 "/chats_menubar/menu_conv/menu_conv_invite_participant");
381 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
383 if (tp_chat != NULL) {
384 connection = empathy_tp_chat_get_connection (tp_chat);
386 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
387 (tp_connection_get_status (connection, NULL) ==
388 TP_CONNECTION_STATUS_CONNECTED);
391 gtk_action_set_sensitive (action, sensitive);
395 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
396 EmpathyChatWindow *window)
398 GtkWidget *menu, *submenu, *orig_submenu;
400 menu = gtk_ui_manager_get_widget (priv->ui_manager,
401 "/chats_menubar/menu_contact");
402 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
404 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
405 submenu = empathy_chat_get_contact_menu (priv->current_chat);
406 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
407 gtk_widget_show (menu);
409 tp_g_signal_connect_object (orig_submenu,
411 (GCallback)_submenu_notify_visible_changed_cb,
417 get_all_unread_messages (EmpathyChatWindowPriv *priv)
422 for (l = priv->chats_new_msg; l != NULL; l = g_list_next (l)) {
423 EmpathyChat *chat = l->data;
425 nb += empathy_chat_get_nb_unread_messages (chat);
432 get_window_title_name (EmpathyChatWindowPriv *priv)
434 const gchar *active_name;
436 guint current_unread_msgs;
438 nb_chats = g_list_length (priv->chats);
439 g_assert (nb_chats > 0);
441 active_name = empathy_chat_get_name (priv->current_chat);
443 current_unread_msgs = empathy_chat_get_nb_unread_messages (
448 if (current_unread_msgs == 0)
449 return g_strdup (active_name);
451 return g_strdup_printf (ngettext (
453 "%s (%d unread)", current_unread_msgs),
454 active_name, current_unread_msgs);
456 guint nb_others = nb_chats - 1;
457 guint all_unread_msgs;
459 all_unread_msgs = get_all_unread_messages (priv);
461 if (all_unread_msgs == 0) {
462 /* no unread message */
463 return g_strdup_printf (ngettext (
465 "%s (and %u others)", nb_others),
466 active_name, nb_others);
469 else if (all_unread_msgs == current_unread_msgs) {
470 /* unread messages are in the current tab */
471 return g_strdup_printf (ngettext (
473 "%s (%d unread)", current_unread_msgs),
474 active_name, current_unread_msgs);
477 else if (current_unread_msgs == 0) {
478 /* unread messages are in other tabs */
479 return g_strdup_printf (ngettext (
480 "%s (%d unread from others)",
481 "%s (%d unread from others)",
483 active_name, all_unread_msgs);
487 /* unread messages are in all the tabs */
488 return g_strdup_printf (ngettext (
489 "%s (%d unread from all)",
490 "%s (%d unread from all)",
492 active_name, all_unread_msgs);
498 chat_window_title_update (EmpathyChatWindowPriv *priv)
502 name = get_window_title_name (priv);
503 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
508 chat_window_icon_update (EmpathyChatWindowPriv *priv)
511 EmpathyContact *remote_contact;
512 gboolean avatar_in_icon;
515 n_chats = g_list_length (priv->chats);
517 /* Update window icon */
518 if (priv->chats_new_msg) {
519 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
520 EMPATHY_IMAGE_MESSAGE);
522 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
524 avatar_in_icon = g_settings_get_boolean (gsettings,
525 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
527 if (n_chats == 1 && avatar_in_icon) {
528 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
529 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
530 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
533 g_object_unref (icon);
536 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
539 g_object_unref (gsettings);
544 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
548 GtkWidget *chat_close_button;
551 if (num_pages == 1) {
552 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
553 chat_close_button = g_object_get_data (G_OBJECT (chat),
554 "chat-window-tab-close-button");
555 gtk_widget_hide (chat_close_button);
557 for (i=0; i<num_pages; i++) {
558 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
559 chat_close_button = g_object_get_data (G_OBJECT (chat),
560 "chat-window-tab-close-button");
561 gtk_widget_show (chat_close_button);
567 chat_window_update (EmpathyChatWindow *window)
569 EmpathyChatWindowPriv *priv = GET_PRIV (window);
572 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
574 /* Update Tab menu */
575 chat_window_menu_context_update (priv,
578 chat_window_conversation_menu_update (priv, window);
580 chat_window_contact_menu_update (priv,
583 chat_window_title_update (priv);
585 chat_window_icon_update (priv);
587 chat_window_close_button_update (priv,
592 append_markup_printf (GString *string,
599 va_start (args, format);
601 tmp = g_markup_vprintf_escaped (format, args);
602 g_string_append (string, tmp);
609 chat_window_update_chat_tab (EmpathyChat *chat)
611 EmpathyChatWindow *window;
612 EmpathyChatWindowPriv *priv;
613 EmpathyContact *remote_contact;
617 const gchar *subject;
618 const gchar *status = NULL;
622 const gchar *icon_name;
623 GtkWidget *tab_image;
624 GtkWidget *menu_image;
626 window = chat_window_find_chat (chat);
630 priv = GET_PRIV (window);
632 /* Get information */
633 name = empathy_chat_get_name (chat);
634 account = empathy_chat_get_account (chat);
635 subject = empathy_chat_get_subject (chat);
636 remote_contact = empathy_chat_get_remote_contact (chat);
638 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
639 name, tp_proxy_get_object_path (account), subject, remote_contact);
641 /* Update tab image */
642 if (empathy_chat_get_tp_chat (chat) == NULL) {
643 /* No TpChat, we are disconnected */
646 else if (g_list_find (priv->chats_new_msg, chat)) {
647 icon_name = EMPATHY_IMAGE_MESSAGE;
649 else if (g_list_find (priv->chats_composing, chat)) {
650 icon_name = EMPATHY_IMAGE_TYPING;
652 else if (remote_contact) {
653 icon_name = empathy_icon_name_for_contact (remote_contact);
655 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
658 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
659 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
660 if (icon_name != NULL) {
661 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
662 gtk_widget_show (tab_image);
663 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
664 gtk_widget_show (menu_image);
666 gtk_widget_hide (tab_image);
667 gtk_widget_hide (menu_image);
670 /* Update tab tooltip */
671 tooltip = g_string_new (NULL);
673 if (remote_contact) {
674 id = empathy_contact_get_id (remote_contact);
675 status = empathy_contact_get_presence_message (remote_contact);
680 append_markup_printf (tooltip,
681 "<b>%s</b><small> (%s)</small>",
683 tp_account_get_display_name (account));
685 if (!EMP_STR_EMPTY (status)) {
686 append_markup_printf (tooltip, "\n<i>%s</i>", status);
690 append_markup_printf (tooltip, "\n<b>%s</b> %s",
691 _("Topic:"), subject);
694 if (g_list_find (priv->chats_composing, chat)) {
695 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
698 markup = g_string_free (tooltip, FALSE);
699 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
700 gtk_widget_set_tooltip_markup (widget, markup);
701 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
702 gtk_widget_set_tooltip_markup (widget, markup);
705 /* Update tab and menu label */
706 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
707 gtk_label_set_text (GTK_LABEL (widget), name);
708 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
709 gtk_label_set_text (GTK_LABEL (widget), name);
711 /* Update the window if it's the current chat */
712 if (priv->current_chat == chat) {
713 chat_window_update (window);
718 chat_window_chat_notify_cb (EmpathyChat *chat)
720 EmpathyContact *old_remote_contact;
721 EmpathyContact *remote_contact = NULL;
723 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
724 remote_contact = empathy_chat_get_remote_contact (chat);
726 if (old_remote_contact != remote_contact) {
727 /* The remote-contact associated with the chat changed, we need
728 * to keep track of any change of that contact and update the
729 * window each time. */
730 if (remote_contact) {
731 g_signal_connect_swapped (remote_contact, "notify",
732 G_CALLBACK (chat_window_update_chat_tab),
735 if (old_remote_contact) {
736 g_signal_handlers_disconnect_by_func (old_remote_contact,
737 chat_window_update_chat_tab,
741 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
742 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
745 chat_window_update_chat_tab (chat);
749 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
750 EmpathySmiley *smiley,
753 EmpathyChatWindowPriv *priv = GET_PRIV (window);
755 GtkTextBuffer *buffer;
758 chat = priv->current_chat;
760 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
761 gtk_text_buffer_get_end_iter (buffer, &iter);
762 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
766 chat_window_conv_activate_cb (GtkAction *action,
767 EmpathyChatWindow *window)
769 EmpathyChatWindowPriv *priv = GET_PRIV (window);
772 EmpathyContact *remote_contact = NULL;
774 /* Favorite room menu */
775 is_room = empathy_chat_is_room (priv->current_chat);
779 gboolean found = FALSE;
780 EmpathyChatroom *chatroom;
782 room = empathy_chat_get_id (priv->current_chat);
783 account = empathy_chat_get_account (priv->current_chat);
784 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
786 if (chatroom != NULL)
787 found = empathy_chatroom_is_favorite (chatroom);
789 DEBUG ("This room %s favorite", found ? "is" : "is not");
790 gtk_toggle_action_set_active (
791 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
793 if (chatroom != NULL)
794 found = empathy_chatroom_is_always_urgent (chatroom);
796 gtk_toggle_action_set_active (
797 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
800 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
801 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
803 /* Show contacts menu */
804 g_object_get (priv->current_chat,
805 "remote-contact", &remote_contact,
806 "show-contacts", &active,
808 if (remote_contact == NULL) {
809 gtk_toggle_action_set_active (
810 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
813 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
814 (remote_contact == NULL));
815 if (remote_contact != NULL) {
816 g_object_unref (remote_contact);
821 chat_window_clear_activate_cb (GtkAction *action,
822 EmpathyChatWindow *window)
824 EmpathyChatWindowPriv *priv = GET_PRIV (window);
826 empathy_chat_clear (priv->current_chat);
830 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
831 EmpathyChatWindow *window)
833 EmpathyChatWindowPriv *priv = GET_PRIV (window);
837 EmpathyChatroom *chatroom;
839 active = gtk_toggle_action_get_active (toggle_action);
840 account = empathy_chat_get_account (priv->current_chat);
841 room = empathy_chat_get_id (priv->current_chat);
843 chatroom = empathy_chatroom_manager_ensure_chatroom (
844 priv->chatroom_manager,
847 empathy_chat_get_name (priv->current_chat));
849 empathy_chatroom_set_favorite (chatroom, active);
850 g_object_unref (chatroom);
854 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
855 EmpathyChatWindow *window)
857 EmpathyChatWindowPriv *priv = GET_PRIV (window);
861 EmpathyChatroom *chatroom;
863 active = gtk_toggle_action_get_active (toggle_action);
864 account = empathy_chat_get_account (priv->current_chat);
865 room = empathy_chat_get_id (priv->current_chat);
867 chatroom = empathy_chatroom_manager_ensure_chatroom (
868 priv->chatroom_manager,
871 empathy_chat_get_name (priv->current_chat));
873 empathy_chatroom_set_always_urgent (chatroom, active);
874 g_object_unref (chatroom);
878 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
879 EmpathyChatWindow *window)
881 EmpathyChatWindowPriv *priv = GET_PRIV (window);
884 active = gtk_toggle_action_get_active (toggle_action);
886 empathy_chat_set_show_contacts (priv->current_chat, active);
890 got_contact_cb (TpConnection *connection,
891 EmpathyContact *contact,
896 EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data);
899 DEBUG ("Failed: %s", error->message);
902 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
903 contact, _("Inviting you to this room"));
908 chat_window_invite_participant_activate_cb (GtkAction *action,
909 EmpathyChatWindow *window)
911 EmpathyChatWindowPriv *priv;
913 EmpathyTpChat *tp_chat;
918 priv = GET_PRIV (window);
920 g_return_if_fail (priv->current_chat != NULL);
922 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
923 channel = empathy_tp_chat_get_channel (tp_chat);
924 account = empathy_chat_get_account (priv->current_chat);
926 dialog = empathy_invite_participant_dialog_new (
927 GTK_WINDOW (priv->dialog), account);
928 gtk_widget_show (dialog);
930 response = gtk_dialog_run (GTK_DIALOG (dialog));
932 if (response == GTK_RESPONSE_ACCEPT) {
933 TpConnection *connection;
936 id = empathy_contact_selector_dialog_get_selected (
937 EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL);
938 if (EMP_STR_EMPTY (id)) goto out;
940 connection = tp_channel_borrow_connection (channel);
941 empathy_tp_contact_factory_get_from_id (connection, id,
942 got_contact_cb, tp_chat, NULL, NULL);
946 gtk_widget_destroy (dialog);
950 chat_window_close_activate_cb (GtkAction *action,
951 EmpathyChatWindow *window)
953 EmpathyChatWindowPriv *priv;
955 priv = GET_PRIV (window);
957 g_return_if_fail (priv->current_chat != NULL);
959 empathy_chat_window_remove_chat (window, priv->current_chat);
963 chat_window_edit_activate_cb (GtkAction *action,
964 EmpathyChatWindow *window)
966 EmpathyChatWindowPriv *priv;
967 GtkClipboard *clipboard;
968 GtkTextBuffer *buffer;
969 gboolean text_available;
971 priv = GET_PRIV (window);
973 g_return_if_fail (priv->current_chat != NULL);
975 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
976 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
977 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
978 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
982 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
983 if (gtk_text_buffer_get_has_selection (buffer)) {
984 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
985 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
989 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
991 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
992 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
995 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
996 text_available = gtk_clipboard_wait_is_text_available (clipboard);
997 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1001 chat_window_cut_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_cut (priv->current_chat);
1014 chat_window_copy_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_copy (priv->current_chat);
1027 chat_window_paste_activate_cb (GtkAction *action,
1028 EmpathyChatWindow *window)
1030 EmpathyChatWindowPriv *priv;
1032 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1034 priv = GET_PRIV (window);
1036 empathy_chat_paste (priv->current_chat);
1040 chat_window_find_activate_cb (GtkAction *action,
1041 EmpathyChatWindow *window)
1043 EmpathyChatWindowPriv *priv;
1045 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1047 priv = GET_PRIV (window);
1049 empathy_chat_find (priv->current_chat);
1053 chat_window_tabs_next_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_ == (numPages - 1) && wrap_around) {
1071 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1075 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1079 chat_window_tabs_previous_activate_cb (GtkAction *action,
1080 EmpathyChatWindow *window)
1082 EmpathyChatWindowPriv *priv;
1084 gint index_, numPages;
1085 gboolean wrap_around;
1087 priv = GET_PRIV (window);
1089 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1090 &wrap_around, NULL);
1092 chat = priv->current_chat;
1093 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1094 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1096 if (index_ <= 0 && wrap_around) {
1097 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1101 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1105 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1106 EmpathyChatWindow *window)
1108 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1109 empathy_chat_manager_undo_closed_chat (priv->chat_manager);
1113 chat_window_tabs_left_activate_cb (GtkAction *action,
1114 EmpathyChatWindow *window)
1116 EmpathyChatWindowPriv *priv;
1118 gint index_, num_pages;
1120 priv = GET_PRIV (window);
1122 chat = priv->current_chat;
1123 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1128 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1132 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1133 chat_window_menu_context_update (priv, num_pages);
1137 chat_window_tabs_right_activate_cb (GtkAction *action,
1138 EmpathyChatWindow *window)
1140 EmpathyChatWindowPriv *priv;
1142 gint index_, num_pages;
1144 priv = GET_PRIV (window);
1146 chat = priv->current_chat;
1147 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1149 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1153 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1154 chat_window_menu_context_update (priv, num_pages);
1158 chat_window_detach_activate_cb (GtkAction *action,
1159 EmpathyChatWindow *window)
1161 EmpathyChatWindowPriv *priv;
1162 EmpathyChatWindow *new_window;
1165 priv = GET_PRIV (window);
1167 chat = priv->current_chat;
1168 new_window = empathy_chat_window_new ();
1170 empathy_chat_window_move_chat (window, new_window, chat);
1172 priv = GET_PRIV (new_window);
1173 gtk_widget_show (priv->dialog);
1177 chat_window_help_contents_activate_cb (GtkAction *action,
1178 EmpathyChatWindow *window)
1180 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1182 empathy_url_show (priv->dialog, "ghelp:empathy");
1186 chat_window_help_about_activate_cb (GtkAction *action,
1187 EmpathyChatWindow *window)
1189 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1191 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1195 chat_window_delete_event_cb (GtkWidget *dialog,
1197 EmpathyChatWindow *window)
1199 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1201 DEBUG ("Delete event received");
1203 g_object_ref (window);
1204 while (priv->chats) {
1205 empathy_chat_window_remove_chat (window, priv->chats->data);
1207 g_object_unref (window);
1213 chat_window_composing_cb (EmpathyChat *chat,
1214 gboolean is_composing,
1215 EmpathyChatWindow *window)
1217 EmpathyChatWindowPriv *priv;
1219 priv = GET_PRIV (window);
1221 if (is_composing && !g_list_find (priv->chats_composing, chat)) {
1222 priv->chats_composing = g_list_prepend (priv->chats_composing, chat);
1224 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1227 chat_window_update_chat_tab (chat);
1231 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1234 EmpathyChatWindowPriv *priv;
1236 priv = GET_PRIV (window);
1238 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1242 chat_window_notification_closed_cb (NotifyNotification *notify,
1243 EmpathyChatWindow *self)
1245 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1247 g_object_unref (notify);
1248 if (priv->notification == notify) {
1249 priv->notification = NULL;
1254 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1255 EmpathyMessage *message,
1258 EmpathyContact *sender;
1259 const gchar *header;
1263 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1264 gboolean res, has_x_canonical_append;
1265 NotifyNotification *notification = priv->notification;
1267 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1270 GSettings *gsettings = g_settings_new (
1271 EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
1273 res = g_settings_get_boolean (gsettings,
1274 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1276 g_object_unref (gsettings);
1283 sender = empathy_message_get_sender (message);
1284 header = empathy_contact_get_name (sender);
1285 body = empathy_message_get_body (message);
1286 escaped = g_markup_escape_text (body, -1);
1287 has_x_canonical_append = empathy_notify_manager_has_capability (
1288 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1290 if (notification != NULL && !has_x_canonical_append) {
1291 /* if the notification server supports x-canonical-append, it is
1292 better to not use notify_notification_update to avoid
1293 overwriting the current notification message */
1294 notify_notification_update (notification,
1295 header, escaped, NULL);
1297 /* if the notification server supports x-canonical-append,
1298 the hint will be added, so that the message from the
1299 just created notification will be automatically appended
1300 to an existing notification with the same title.
1301 In this way the previous message will not be lost: the new
1302 message will appear below it, in the same notification */
1303 notification = notify_notification_new (header, escaped, NULL, NULL);
1305 if (priv->notification == NULL) {
1306 priv->notification = notification;
1309 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1311 tp_g_signal_connect_object (notification, "closed",
1312 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1314 if (has_x_canonical_append) {
1315 notify_notification_set_hint_string (notification,
1316 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
1320 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1321 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1323 if (pixbuf != NULL) {
1324 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1325 g_object_unref (pixbuf);
1328 notify_notification_show (notification, NULL);
1334 chat_window_set_highlight_room_tab_label (EmpathyChat *chat)
1339 if (!empathy_chat_is_room (chat))
1342 markup = g_markup_printf_escaped (
1343 "<span color=\"red\" weight=\"bold\">%s</span>",
1344 empathy_chat_get_name (chat));
1346 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1347 gtk_label_set_markup (GTK_LABEL (widget), markup);
1352 chat_window_new_message_cb (EmpathyChat *chat,
1353 EmpathyMessage *message,
1354 EmpathyChatWindow *window)
1356 EmpathyChatWindowPriv *priv;
1358 gboolean needs_urgency;
1359 EmpathyContact *sender;
1361 priv = GET_PRIV (window);
1363 has_focus = empathy_chat_window_has_focus (window);
1365 /* - if we're the sender, we play the sound if it's specified in the
1366 * preferences and we're not away.
1367 * - if we receive a message, we play the sound if it's specified in the
1368 * preferences and the window does not have focus on the chat receiving
1372 sender = empathy_message_get_sender (message);
1374 if (empathy_contact_is_user (sender)) {
1375 empathy_sound_play (GTK_WIDGET (priv->dialog),
1376 EMPATHY_SOUND_MESSAGE_OUTGOING);
1379 if (has_focus && priv->current_chat == chat) {
1380 /* window and tab are focused so consider the message to be read */
1382 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1383 empathy_chat_messages_read (chat);
1387 if (!g_list_find (priv->chats_new_msg, chat)) {
1388 priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
1389 chat_window_update_chat_tab (chat);
1392 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1393 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1394 * an unamed MUC (msn-like).
1395 * In case of a MUC, we set urgency if either:
1396 * a) the chatroom's always_urgent property is TRUE
1397 * b) the message contains our alias
1399 if (empathy_chat_is_room (chat) ||
1400 empathy_chat_get_remote_contact (chat) == NULL) {
1403 EmpathyChatroom *chatroom;
1405 account = empathy_chat_get_account (chat);
1406 room = empathy_chat_get_id (chat);
1408 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1411 if (empathy_chatroom_is_always_urgent (chatroom)) {
1412 needs_urgency = TRUE;
1414 needs_urgency = empathy_message_should_highlight (message);
1417 needs_urgency = TRUE;
1420 if (needs_urgency) {
1422 chat_window_set_urgency_hint (window, TRUE);
1423 chat_window_set_highlight_room_tab_label (chat);
1426 empathy_sound_play (GTK_WIDGET (priv->dialog),
1427 EMPATHY_SOUND_MESSAGE_INCOMING);
1428 chat_window_show_or_update_notification (window, message, chat);
1431 /* update the number of unread messages */
1432 chat_window_title_update (priv);
1435 static GtkNotebook *
1436 chat_window_detach_hook (GtkNotebook *source,
1442 EmpathyChatWindowPriv *priv;
1443 EmpathyChatWindow *window, *new_window;
1446 chat = EMPATHY_CHAT (page);
1447 window = chat_window_find_chat (chat);
1449 new_window = empathy_chat_window_new ();
1450 priv = GET_PRIV (new_window);
1452 DEBUG ("Detach hook called");
1454 empathy_chat_window_move_chat (window, new_window, chat);
1456 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1457 gtk_widget_show (priv->dialog);
1463 chat_window_page_switched_cb (GtkNotebook *notebook,
1464 GtkNotebookPage *page,
1466 EmpathyChatWindow *window)
1468 EmpathyChatWindowPriv *priv;
1472 DEBUG ("Page switched");
1474 priv = GET_PRIV (window);
1476 child = gtk_notebook_get_nth_page (notebook, page_num);
1477 chat = EMPATHY_CHAT (child);
1479 if (priv->page_added) {
1480 priv->page_added = FALSE;
1481 empathy_chat_scroll_down (chat);
1483 else if (priv->current_chat == chat) {
1487 priv->current_chat = chat;
1488 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1489 empathy_chat_messages_read (chat);
1491 chat_window_update_chat_tab (chat);
1495 chat_window_page_added_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 added (back to the same window)");
1511 priv->dnd_same_window = FALSE;
1515 DEBUG ("Page added");
1517 /* Get chat object */
1518 chat = EMPATHY_CHAT (child);
1520 /* Connect chat signals for this window */
1521 g_signal_connect (chat, "composing",
1522 G_CALLBACK (chat_window_composing_cb),
1524 g_signal_connect (chat, "new-message",
1525 G_CALLBACK (chat_window_new_message_cb),
1527 g_signal_connect (chat, "notify::tp-chat",
1528 G_CALLBACK (chat_window_update_chat_tab),
1531 /* Set flag so we know to perform some special operations on
1532 * switch page due to the new page being added.
1534 priv->page_added = TRUE;
1536 /* Get list of chats up to date */
1537 priv->chats = g_list_append (priv->chats, chat);
1539 chat_window_update_chat_tab (chat);
1543 chat_window_page_removed_cb (GtkNotebook *notebook,
1546 EmpathyChatWindow *window)
1548 EmpathyChatWindowPriv *priv;
1551 priv = GET_PRIV (window);
1553 /* If we just received DND to the same window, we don't want
1554 * to do anything here like removing the tab and then readding
1555 * it, so we return here and in "page-added".
1557 if (priv->dnd_same_window) {
1558 DEBUG ("Page removed (and will be readded to same window)");
1562 DEBUG ("Page removed");
1564 /* Get chat object */
1565 chat = EMPATHY_CHAT (child);
1567 /* Disconnect all signal handlers for this chat and this window */
1568 g_signal_handlers_disconnect_by_func (chat,
1569 G_CALLBACK (chat_window_composing_cb),
1571 g_signal_handlers_disconnect_by_func (chat,
1572 G_CALLBACK (chat_window_new_message_cb),
1574 g_signal_handlers_disconnect_by_func (chat,
1575 G_CALLBACK (chat_window_update_chat_tab),
1578 /* Keep list of chats up to date */
1579 priv->chats = g_list_remove (priv->chats, chat);
1580 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1581 empathy_chat_messages_read (chat);
1582 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1584 if (priv->chats == NULL) {
1585 g_object_unref (window);
1587 chat_window_update (window);
1592 chat_window_focus_in_event_cb (GtkWidget *widget,
1594 EmpathyChatWindow *window)
1596 EmpathyChatWindowPriv *priv;
1598 priv = GET_PRIV (window);
1600 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
1601 empathy_chat_messages_read (priv->current_chat);
1603 chat_window_set_urgency_hint (window, FALSE);
1605 /* Update the title, since we now mark all unread messages as read. */
1606 chat_window_update_chat_tab (priv->current_chat);
1612 chat_window_drag_drop (GtkWidget *widget,
1613 GdkDragContext *context,
1617 EmpathyChatWindow *window)
1620 EmpathyChatWindowPriv *priv;
1622 priv = GET_PRIV (window);
1624 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1625 if (target == GDK_NONE)
1626 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1628 if (target != GDK_NONE) {
1629 gtk_drag_get_data (widget, context, target, time_);
1637 chat_window_drag_motion (GtkWidget *widget,
1638 GdkDragContext *context,
1642 EmpathyChatWindow *window)
1645 EmpathyChatWindowPriv *priv;
1647 priv = GET_PRIV (window);
1649 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1650 if (target != GDK_NONE) {
1651 /* This is a file drag. Ensure the contact is online and set the
1652 drag type to COPY. Note that it's possible that the tab will
1653 be switched by GTK+ after a timeout from drag_motion without
1654 getting another drag_motion to disable the drop. You have
1655 to hold your mouse really still.
1657 EmpathyContact *contact;
1659 priv = GET_PRIV (window);
1660 contact = empathy_chat_get_remote_contact (priv->current_chat);
1661 /* contact is NULL for multi-user chats. We don't do
1662 * file transfers to MUCs. We also don't send files
1663 * to offline contacts or contacts that don't support
1666 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1667 gdk_drag_status (context, 0, time_);
1670 if (!(empathy_contact_get_capabilities (contact)
1671 & EMPATHY_CAPABILITIES_FT)) {
1672 gdk_drag_status (context, 0, time_);
1675 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1679 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1680 if (target != GDK_NONE) {
1681 /* This is a drag of a contact from a contact list. Set to COPY.
1682 FIXME: If this drag is to a MUC window, it invites the user.
1683 Otherwise, it opens a chat. Should we use a different drag
1684 type for invites? Should we allow ASK?
1686 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1694 chat_window_drag_data_received (GtkWidget *widget,
1695 GdkDragContext *context,
1698 GtkSelectionData *selection,
1701 EmpathyChatWindow *window)
1703 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1704 EmpathyChat *chat = NULL;
1705 EmpathyChatWindow *old_window;
1706 TpAccount *account = NULL;
1707 TpAccountManager *account_manager;
1710 const gchar *account_id;
1711 const gchar *contact_id;
1713 id = (const gchar*) gtk_selection_data_get_data (selection);
1715 /* FIXME: Perhaps should be sure that the account manager is
1716 * prepared before calling _ensure_account on it. */
1717 account_manager = tp_account_manager_dup ();
1719 DEBUG ("DND contact from roster with id:'%s'", id);
1721 strv = g_strsplit (id, ":", 2);
1722 if (g_strv_length (strv) == 2) {
1723 account_id = strv[0];
1724 contact_id = strv[1];
1726 tp_account_manager_ensure_account (account_manager, account_id);
1727 if (account != NULL)
1728 chat = empathy_chat_window_find_chat (account, contact_id);
1731 if (account == NULL) {
1733 gtk_drag_finish (context, FALSE, FALSE, time_);
1738 TpConnection *connection;
1740 connection = tp_account_get_connection (account);
1743 empathy_dispatcher_chat_with_contact_id (
1744 connection, contact_id, gtk_get_current_event_time (), NULL, NULL);
1750 g_object_unref (account_manager);
1753 old_window = chat_window_find_chat (chat);
1755 if (old_window == window) {
1756 gtk_drag_finish (context, TRUE, FALSE, time_);
1760 empathy_chat_window_move_chat (old_window, window, chat);
1762 empathy_chat_window_add_chat (window, chat);
1765 /* Added to take care of any outstanding chat events */
1766 empathy_chat_window_present_chat (chat,
1767 EMPATHY_DISPATCHER_NON_USER_ACTION);
1769 /* We should return TRUE to remove the data when doing
1770 * GDK_ACTION_MOVE, but we don't here otherwise it has
1771 * weird consequences, and we handle that internally
1772 * anyway with add_chat () and remove_chat ().
1774 gtk_drag_finish (context, TRUE, FALSE, time_);
1776 else if (info == DND_DRAG_TYPE_URI_LIST) {
1777 EmpathyChatWindowPriv *priv;
1778 EmpathyContact *contact;
1781 priv = GET_PRIV (window);
1782 contact = empathy_chat_get_remote_contact (priv->current_chat);
1784 /* contact is NULL when current_chat is a multi-user chat.
1785 * We don't do file transfers to MUCs, so just cancel the drag.
1787 if (contact == NULL) {
1788 gtk_drag_finish (context, TRUE, FALSE, time_);
1792 data = (const gchar *) gtk_selection_data_get_data (selection);
1793 empathy_send_file_from_uri_list (contact, data);
1795 gtk_drag_finish (context, TRUE, FALSE, time_);
1797 else if (info == DND_DRAG_TYPE_TAB) {
1799 EmpathyChatWindow *old_window = NULL;
1803 chat = (void *) gtk_selection_data_get_data (selection);
1804 old_window = chat_window_find_chat (*chat);
1807 EmpathyChatWindowPriv *priv;
1809 priv = GET_PRIV (window);
1810 priv->dnd_same_window = (old_window == window);
1811 DEBUG ("DND tab (within same window: %s)",
1812 priv->dnd_same_window ? "Yes" : "No");
1815 DEBUG ("DND from unknown source");
1816 gtk_drag_finish (context, FALSE, FALSE, time_);
1821 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1822 guint num_chats_in_manager,
1823 EmpathyChatWindow *window)
1825 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1827 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1828 num_chats_in_manager > 0);
1832 chat_window_finalize (GObject *object)
1834 EmpathyChatWindow *window;
1835 EmpathyChatWindowPriv *priv;
1837 window = EMPATHY_CHAT_WINDOW (object);
1838 priv = GET_PRIV (window);
1840 DEBUG ("Finalized: %p", object);
1842 g_object_unref (priv->ui_manager);
1843 g_object_unref (priv->chatroom_manager);
1844 g_object_unref (priv->notify_mgr);
1846 if (priv->notification != NULL) {
1847 notify_notification_close (priv->notification, NULL);
1848 priv->notification = NULL;
1851 if (priv->contact_targets) {
1852 gtk_target_list_unref (priv->contact_targets);
1854 if (priv->file_targets) {
1855 gtk_target_list_unref (priv->file_targets);
1858 if (priv->chat_manager) {
1859 g_signal_handler_disconnect (priv->chat_manager,
1860 priv->chat_manager_chats_changed_id);
1861 g_object_unref (priv->chat_manager);
1862 priv->chat_manager = NULL;
1865 chat_windows = g_list_remove (chat_windows, window);
1866 gtk_widget_destroy (priv->dialog);
1868 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1872 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1874 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1876 object_class->finalize = chat_window_finalize;
1878 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1880 /* Set up a style for the close button with no focus padding. */
1881 gtk_rc_parse_string (
1882 "style \"empathy-close-button-style\"\n"
1884 " GtkWidget::focus-padding = 0\n"
1888 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1890 gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1894 empathy_chat_window_init (EmpathyChatWindow *window)
1897 GtkAccelGroup *accel_group;
1902 GtkWidget *chat_vbox;
1904 EmpathySmileyManager *smiley_manager;
1905 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1906 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1908 window->priv = priv;
1909 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1910 gui = empathy_builder_get_file (filename,
1911 "chat_window", &priv->dialog,
1912 "chat_vbox", &chat_vbox,
1913 "ui_manager", &priv->ui_manager,
1914 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1915 "menu_conv_favorite", &priv->menu_conv_favorite,
1916 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
1917 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1918 "menu_edit_cut", &priv->menu_edit_cut,
1919 "menu_edit_copy", &priv->menu_edit_copy,
1920 "menu_edit_paste", &priv->menu_edit_paste,
1921 "menu_edit_find", &priv->menu_edit_find,
1922 "menu_tabs_next", &priv->menu_tabs_next,
1923 "menu_tabs_prev", &priv->menu_tabs_prev,
1924 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1925 "menu_tabs_left", &priv->menu_tabs_left,
1926 "menu_tabs_right", &priv->menu_tabs_right,
1927 "menu_tabs_detach", &priv->menu_tabs_detach,
1931 empathy_builder_connect (gui, window,
1932 "menu_conv", "activate", chat_window_conv_activate_cb,
1933 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1934 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1935 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
1936 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1937 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1938 "menu_conv_close", "activate", chat_window_close_activate_cb,
1939 "menu_edit", "activate", chat_window_edit_activate_cb,
1940 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1941 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1942 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1943 "menu_edit_find", "activate", chat_window_find_activate_cb,
1944 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1945 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1946 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1947 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1948 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1949 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1950 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1951 "menu_help_about", "activate", chat_window_help_about_activate_cb,
1954 g_object_ref (priv->ui_manager);
1955 g_object_unref (gui);
1957 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1959 priv->notebook = gtk_notebook_new ();
1960 gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
1961 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
1962 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
1963 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1964 gtk_widget_show (priv->notebook);
1967 accel_group = gtk_accel_group_new ();
1968 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
1970 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
1971 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
1974 gtk_accel_group_connect (accel_group,
1981 g_object_unref (accel_group);
1983 /* Set up drag target lists */
1984 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
1985 G_N_ELEMENTS (drag_types_dest_contact));
1986 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1987 G_N_ELEMENTS (drag_types_dest_file));
1989 /* Set up smiley menu */
1990 smiley_manager = empathy_smiley_manager_dup_singleton ();
1991 submenu = empathy_smiley_menu_new (smiley_manager,
1992 chat_window_insert_smiley_activate_cb,
1994 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1995 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
1996 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
1997 g_object_unref (smiley_manager);
1999 /* Set up signals we can't do with ui file since we may need to
2000 * block/unblock them at some later stage.
2003 g_signal_connect (priv->dialog,
2005 G_CALLBACK (chat_window_delete_event_cb),
2007 g_signal_connect (priv->dialog,
2009 G_CALLBACK (chat_window_focus_in_event_cb),
2011 g_signal_connect_after (priv->notebook,
2013 G_CALLBACK (chat_window_page_switched_cb),
2015 g_signal_connect (priv->notebook,
2017 G_CALLBACK (chat_window_page_added_cb),
2019 g_signal_connect (priv->notebook,
2021 G_CALLBACK (chat_window_page_removed_cb),
2024 /* Set up drag and drop */
2025 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2026 GTK_DEST_DEFAULT_HIGHLIGHT,
2028 G_N_ELEMENTS (drag_types_dest),
2029 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2031 /* connect_after to allow GtkNotebook's built-in tab switching */
2032 g_signal_connect_after (priv->notebook,
2034 G_CALLBACK (chat_window_drag_motion),
2036 g_signal_connect (priv->notebook,
2037 "drag-data-received",
2038 G_CALLBACK (chat_window_drag_data_received),
2040 g_signal_connect (priv->notebook,
2042 G_CALLBACK (chat_window_drag_drop),
2045 chat_windows = g_list_prepend (chat_windows, window);
2047 /* Set up private details */
2049 priv->chats_new_msg = NULL;
2050 priv->chats_composing = NULL;
2051 priv->current_chat = NULL;
2052 priv->notification = NULL;
2054 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2056 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2057 priv->chat_manager_chats_changed_id =
2058 g_signal_connect (priv->chat_manager, "chats-changed",
2059 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2062 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2063 empathy_chat_manager_get_num_chats (priv->chat_manager),
2068 empathy_chat_window_new (void)
2070 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
2073 /* Returns the window to open a new tab in if there is only one window
2074 * visble, otherwise, returns NULL indicating that a new window should
2078 empathy_chat_window_get_default (gboolean room)
2080 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2082 gboolean separate_windows = TRUE;
2084 separate_windows = g_settings_get_boolean (gsettings,
2085 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2087 g_object_unref (gsettings);
2089 if (separate_windows) {
2090 /* Always create a new window */
2094 for (l = chat_windows; l; l = l->next) {
2095 EmpathyChatWindowPriv *priv;
2096 EmpathyChatWindow *chat_window;
2099 chat_window = l->data;
2100 priv = GET_PRIV (chat_window);
2102 dialog = empathy_chat_window_get_dialog (chat_window);
2103 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
2104 guint nb_rooms, nb_private;
2105 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2107 /* Skip the window if there aren't any rooms in it */
2108 if (room && nb_rooms == 0)
2111 /* Skip the window if there aren't any 1-1 chats in it */
2112 if (!room && nb_private == 0)
2115 /* Found a visible window on this desktop */
2124 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2126 EmpathyChatWindowPriv *priv;
2128 g_return_val_if_fail (window != NULL, NULL);
2130 priv = GET_PRIV (window);
2132 return priv->dialog;
2136 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2139 EmpathyChatWindowPriv *priv;
2141 GtkWidget *popup_label;
2143 GValue value = { 0, };
2145 g_return_if_fail (window != NULL);
2146 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2148 priv = GET_PRIV (window);
2150 /* Reference the chat object */
2151 g_object_ref (chat);
2153 /* If this window has just been created, position it */
2154 if (priv->chats == NULL) {
2155 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2156 const gchar *name = "chat-window";
2157 gboolean separate_windows;
2159 separate_windows = g_settings_get_boolean (gsettings,
2160 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2162 g_object_unref (gsettings);
2164 if (separate_windows) {
2165 name = empathy_chat_get_id (chat);
2167 else if (empathy_chat_is_room (chat)) {
2168 name = "room-window";
2171 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2174 child = GTK_WIDGET (chat);
2175 label = chat_window_create_label (window, chat, TRUE);
2176 popup_label = chat_window_create_label (window, chat, FALSE);
2177 gtk_widget_show (child);
2179 g_signal_connect (chat, "notify::name",
2180 G_CALLBACK (chat_window_chat_notify_cb),
2182 g_signal_connect (chat, "notify::subject",
2183 G_CALLBACK (chat_window_chat_notify_cb),
2185 g_signal_connect (chat, "notify::remote-contact",
2186 G_CALLBACK (chat_window_chat_notify_cb),
2188 chat_window_chat_notify_cb (chat);
2190 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2191 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2192 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2193 g_value_init (&value, G_TYPE_BOOLEAN);
2194 g_value_set_boolean (&value, TRUE);
2195 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2196 child, "tab-expand" , &value);
2197 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2198 child, "tab-fill" , &value);
2199 g_value_unset (&value);
2201 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2205 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2208 EmpathyChatWindowPriv *priv;
2210 EmpathyContact *remote_contact;
2211 EmpathyChatManager *chat_manager;
2213 g_return_if_fail (window != NULL);
2214 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2216 priv = GET_PRIV (window);
2218 g_signal_handlers_disconnect_by_func (chat,
2219 chat_window_chat_notify_cb,
2221 remote_contact = g_object_get_data (G_OBJECT (chat),
2222 "chat-window-remote-contact");
2223 if (remote_contact) {
2224 g_signal_handlers_disconnect_by_func (remote_contact,
2225 chat_window_update_chat_tab,
2229 chat_manager = empathy_chat_manager_dup_singleton ();
2230 empathy_chat_manager_closed_chat (chat_manager, chat);
2231 g_object_unref (chat_manager);
2233 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2235 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2237 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2239 g_object_unref (chat);
2243 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2244 EmpathyChatWindow *new_window,
2249 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2250 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2251 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2253 widget = GTK_WIDGET (chat);
2255 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2256 G_OBJECT (widget)->ref_count);
2258 /* We reference here to make sure we don't loose the widget
2259 * and the EmpathyChat object during the move.
2261 g_object_ref (chat);
2262 g_object_ref (widget);
2264 empathy_chat_window_remove_chat (old_window, chat);
2265 empathy_chat_window_add_chat (new_window, chat);
2267 g_object_unref (widget);
2268 g_object_unref (chat);
2272 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2275 EmpathyChatWindowPriv *priv;
2278 g_return_if_fail (window != NULL);
2279 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2281 priv = GET_PRIV (window);
2283 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2285 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2290 empathy_chat_window_has_focus (EmpathyChatWindow *window)
2292 EmpathyChatWindowPriv *priv;
2295 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
2297 priv = GET_PRIV (window);
2299 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
2305 empathy_chat_window_find_chat (TpAccount *account,
2310 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2312 for (l = chat_windows; l; l = l->next) {
2313 EmpathyChatWindowPriv *priv;
2314 EmpathyChatWindow *window;
2318 priv = GET_PRIV (window);
2320 for (ll = priv->chats; ll; ll = ll->next) {
2325 if (account == empathy_chat_get_account (chat) &&
2326 !tp_strdiff (id, empathy_chat_get_id (chat))) {
2336 empathy_chat_window_present_chat (EmpathyChat *chat,
2339 EmpathyChatWindow *window;
2340 EmpathyChatWindowPriv *priv;
2341 guint32 x_timestamp;
2343 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2345 window = chat_window_find_chat (chat);
2347 /* If the chat has no window, create one */
2348 if (window == NULL) {
2349 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2351 window = empathy_chat_window_new ();
2352 gtk_widget_show_all (GET_PRIV (window)->dialog);
2355 empathy_chat_window_add_chat (window, chat);
2358 /* Don't force the window to show itself when it wasn't
2359 * an action by the user
2361 if (timestamp == EMPATHY_DISPATCHER_NON_USER_ACTION)
2364 priv = GET_PRIV (window);
2366 if (timestamp == EMPATHY_DISPATCHER_CURRENT_TIME) {
2367 x_timestamp = GDK_CURRENT_TIME;
2369 x_timestamp = CLAMP (timestamp, 0, G_MAXUINT32);
2370 /* Don't present or switch tab if the action was earlier than the
2371 * last actions X time, accounting for overflow and the first ever
2374 if (priv->x_user_action_time != 0
2375 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2378 priv->x_user_action_time = x_timestamp;
2381 empathy_chat_window_switch_to_chat (window, chat);
2382 empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2385 gtk_widget_grab_focus (chat->input_text_view);
2389 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2393 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2395 guint _nb_rooms = 0, _nb_private = 0;
2397 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2398 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2404 if (nb_rooms != NULL)
2405 *nb_rooms = _nb_rooms;
2406 if (nb_private != NULL)
2407 *nb_private = _nb_private;