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, 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_alias (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 gpointer ignore, /* see note below */
1466 EmpathyChatWindow *window)
1468 EmpathyChatWindowPriv *priv;
1472 DEBUG ("Page switched");
1474 priv = GET_PRIV (window);
1476 /* N.B. in GTK+ 3 child is passed as the first argument to the signal,
1477 * but we can't use that while trying to retain GTK+ 2.x compatibility.
1479 child = gtk_notebook_get_nth_page (notebook, page_num);
1480 chat = EMPATHY_CHAT (child);
1482 if (priv->page_added) {
1483 priv->page_added = FALSE;
1484 empathy_chat_scroll_down (chat);
1486 else if (priv->current_chat == chat) {
1490 priv->current_chat = chat;
1491 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1492 empathy_chat_messages_read (chat);
1494 chat_window_update_chat_tab (chat);
1498 chat_window_page_added_cb (GtkNotebook *notebook,
1501 EmpathyChatWindow *window)
1503 EmpathyChatWindowPriv *priv;
1506 priv = GET_PRIV (window);
1508 /* If we just received DND to the same window, we don't want
1509 * to do anything here like removing the tab and then readding
1510 * it, so we return here and in "page-added".
1512 if (priv->dnd_same_window) {
1513 DEBUG ("Page added (back to the same window)");
1514 priv->dnd_same_window = FALSE;
1518 DEBUG ("Page added");
1520 /* Get chat object */
1521 chat = EMPATHY_CHAT (child);
1523 /* Connect chat signals for this window */
1524 g_signal_connect (chat, "composing",
1525 G_CALLBACK (chat_window_composing_cb),
1527 g_signal_connect (chat, "new-message",
1528 G_CALLBACK (chat_window_new_message_cb),
1530 g_signal_connect (chat, "notify::tp-chat",
1531 G_CALLBACK (chat_window_update_chat_tab),
1534 /* Set flag so we know to perform some special operations on
1535 * switch page due to the new page being added.
1537 priv->page_added = TRUE;
1539 /* Get list of chats up to date */
1540 priv->chats = g_list_append (priv->chats, chat);
1542 chat_window_update_chat_tab (chat);
1546 chat_window_page_removed_cb (GtkNotebook *notebook,
1549 EmpathyChatWindow *window)
1551 EmpathyChatWindowPriv *priv;
1554 priv = GET_PRIV (window);
1556 /* If we just received DND to the same window, we don't want
1557 * to do anything here like removing the tab and then readding
1558 * it, so we return here and in "page-added".
1560 if (priv->dnd_same_window) {
1561 DEBUG ("Page removed (and will be readded to same window)");
1565 DEBUG ("Page removed");
1567 /* Get chat object */
1568 chat = EMPATHY_CHAT (child);
1570 /* Disconnect all signal handlers for this chat and this window */
1571 g_signal_handlers_disconnect_by_func (chat,
1572 G_CALLBACK (chat_window_composing_cb),
1574 g_signal_handlers_disconnect_by_func (chat,
1575 G_CALLBACK (chat_window_new_message_cb),
1577 g_signal_handlers_disconnect_by_func (chat,
1578 G_CALLBACK (chat_window_update_chat_tab),
1581 /* Keep list of chats up to date */
1582 priv->chats = g_list_remove (priv->chats, chat);
1583 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1584 empathy_chat_messages_read (chat);
1585 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1587 if (priv->chats == NULL) {
1588 g_object_unref (window);
1590 chat_window_update (window);
1595 chat_window_focus_in_event_cb (GtkWidget *widget,
1597 EmpathyChatWindow *window)
1599 EmpathyChatWindowPriv *priv;
1601 priv = GET_PRIV (window);
1603 priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
1604 empathy_chat_messages_read (priv->current_chat);
1606 chat_window_set_urgency_hint (window, FALSE);
1608 /* Update the title, since we now mark all unread messages as read. */
1609 chat_window_update_chat_tab (priv->current_chat);
1615 chat_window_drag_drop (GtkWidget *widget,
1616 GdkDragContext *context,
1620 EmpathyChatWindow *window)
1623 EmpathyChatWindowPriv *priv;
1625 priv = GET_PRIV (window);
1627 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1628 if (target == GDK_NONE)
1629 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1631 if (target != GDK_NONE) {
1632 gtk_drag_get_data (widget, context, target, time_);
1640 chat_window_drag_motion (GtkWidget *widget,
1641 GdkDragContext *context,
1645 EmpathyChatWindow *window)
1648 EmpathyChatWindowPriv *priv;
1650 priv = GET_PRIV (window);
1652 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1653 if (target != GDK_NONE) {
1654 /* This is a file drag. Ensure the contact is online and set the
1655 drag type to COPY. Note that it's possible that the tab will
1656 be switched by GTK+ after a timeout from drag_motion without
1657 getting another drag_motion to disable the drop. You have
1658 to hold your mouse really still.
1660 EmpathyContact *contact;
1662 priv = GET_PRIV (window);
1663 contact = empathy_chat_get_remote_contact (priv->current_chat);
1664 /* contact is NULL for multi-user chats. We don't do
1665 * file transfers to MUCs. We also don't send files
1666 * to offline contacts or contacts that don't support
1669 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1670 gdk_drag_status (context, 0, time_);
1673 if (!(empathy_contact_get_capabilities (contact)
1674 & EMPATHY_CAPABILITIES_FT)) {
1675 gdk_drag_status (context, 0, time_);
1678 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1682 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1683 if (target != GDK_NONE) {
1684 /* This is a drag of a contact from a contact list. Set to COPY.
1685 FIXME: If this drag is to a MUC window, it invites the user.
1686 Otherwise, it opens a chat. Should we use a different drag
1687 type for invites? Should we allow ASK?
1689 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1697 chat_window_drag_data_received (GtkWidget *widget,
1698 GdkDragContext *context,
1701 GtkSelectionData *selection,
1704 EmpathyChatWindow *window)
1706 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1707 EmpathyChat *chat = NULL;
1708 EmpathyChatWindow *old_window;
1709 TpAccount *account = NULL;
1710 TpAccountManager *account_manager;
1713 const gchar *account_id;
1714 const gchar *contact_id;
1716 id = (const gchar*) gtk_selection_data_get_data (selection);
1718 /* FIXME: Perhaps should be sure that the account manager is
1719 * prepared before calling _ensure_account on it. */
1720 account_manager = tp_account_manager_dup ();
1722 DEBUG ("DND contact from roster with id:'%s'", id);
1724 strv = g_strsplit (id, ":", 2);
1725 if (g_strv_length (strv) == 2) {
1726 account_id = strv[0];
1727 contact_id = strv[1];
1729 tp_account_manager_ensure_account (account_manager, account_id);
1730 if (account != NULL)
1731 chat = empathy_chat_window_find_chat (account, contact_id);
1734 if (account == NULL) {
1736 gtk_drag_finish (context, FALSE, FALSE, time_);
1741 TpConnection *connection;
1743 connection = tp_account_get_connection (account);
1746 empathy_dispatcher_chat_with_contact_id (
1747 connection, contact_id, gtk_get_current_event_time ());
1753 g_object_unref (account_manager);
1756 old_window = chat_window_find_chat (chat);
1758 if (old_window == window) {
1759 gtk_drag_finish (context, TRUE, FALSE, time_);
1763 empathy_chat_window_move_chat (old_window, window, chat);
1765 empathy_chat_window_add_chat (window, chat);
1768 /* Added to take care of any outstanding chat events */
1769 empathy_chat_window_present_chat (chat,
1770 EMPATHY_DISPATCHER_NON_USER_ACTION);
1772 /* We should return TRUE to remove the data when doing
1773 * GDK_ACTION_MOVE, but we don't here otherwise it has
1774 * weird consequences, and we handle that internally
1775 * anyway with add_chat () and remove_chat ().
1777 gtk_drag_finish (context, TRUE, FALSE, time_);
1779 else if (info == DND_DRAG_TYPE_URI_LIST) {
1780 EmpathyChatWindowPriv *priv;
1781 EmpathyContact *contact;
1784 priv = GET_PRIV (window);
1785 contact = empathy_chat_get_remote_contact (priv->current_chat);
1787 /* contact is NULL when current_chat is a multi-user chat.
1788 * We don't do file transfers to MUCs, so just cancel the drag.
1790 if (contact == NULL) {
1791 gtk_drag_finish (context, TRUE, FALSE, time_);
1795 data = (const gchar *) gtk_selection_data_get_data (selection);
1796 empathy_send_file_from_uri_list (contact, data);
1798 gtk_drag_finish (context, TRUE, FALSE, time_);
1800 else if (info == DND_DRAG_TYPE_TAB) {
1802 EmpathyChatWindow *old_window = NULL;
1806 chat = (void *) gtk_selection_data_get_data (selection);
1807 old_window = chat_window_find_chat (*chat);
1810 EmpathyChatWindowPriv *priv;
1812 priv = GET_PRIV (window);
1813 priv->dnd_same_window = (old_window == window);
1814 DEBUG ("DND tab (within same window: %s)",
1815 priv->dnd_same_window ? "Yes" : "No");
1818 DEBUG ("DND from unknown source");
1819 gtk_drag_finish (context, FALSE, FALSE, time_);
1824 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1825 guint num_chats_in_manager,
1826 EmpathyChatWindow *window)
1828 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1830 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1831 num_chats_in_manager > 0);
1835 chat_window_finalize (GObject *object)
1837 EmpathyChatWindow *window;
1838 EmpathyChatWindowPriv *priv;
1840 window = EMPATHY_CHAT_WINDOW (object);
1841 priv = GET_PRIV (window);
1843 DEBUG ("Finalized: %p", object);
1845 g_object_unref (priv->ui_manager);
1846 g_object_unref (priv->chatroom_manager);
1847 g_object_unref (priv->notify_mgr);
1849 if (priv->notification != NULL) {
1850 notify_notification_close (priv->notification, NULL);
1851 priv->notification = NULL;
1854 if (priv->contact_targets) {
1855 gtk_target_list_unref (priv->contact_targets);
1857 if (priv->file_targets) {
1858 gtk_target_list_unref (priv->file_targets);
1861 if (priv->chat_manager) {
1862 g_signal_handler_disconnect (priv->chat_manager,
1863 priv->chat_manager_chats_changed_id);
1864 g_object_unref (priv->chat_manager);
1865 priv->chat_manager = NULL;
1868 chat_windows = g_list_remove (chat_windows, window);
1869 gtk_widget_destroy (priv->dialog);
1871 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1875 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1877 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1879 object_class->finalize = chat_window_finalize;
1881 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1883 /* Set up a style for the close button with no focus padding. */
1884 gtk_rc_parse_string (
1885 "style \"empathy-close-button-style\"\n"
1887 " GtkWidget::focus-padding = 0\n"
1891 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1893 gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1897 empathy_chat_window_init (EmpathyChatWindow *window)
1900 GtkAccelGroup *accel_group;
1905 GtkWidget *chat_vbox;
1907 EmpathySmileyManager *smiley_manager;
1908 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1909 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1911 window->priv = priv;
1912 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1913 gui = empathy_builder_get_file (filename,
1914 "chat_window", &priv->dialog,
1915 "chat_vbox", &chat_vbox,
1916 "ui_manager", &priv->ui_manager,
1917 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1918 "menu_conv_favorite", &priv->menu_conv_favorite,
1919 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
1920 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1921 "menu_edit_cut", &priv->menu_edit_cut,
1922 "menu_edit_copy", &priv->menu_edit_copy,
1923 "menu_edit_paste", &priv->menu_edit_paste,
1924 "menu_edit_find", &priv->menu_edit_find,
1925 "menu_tabs_next", &priv->menu_tabs_next,
1926 "menu_tabs_prev", &priv->menu_tabs_prev,
1927 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1928 "menu_tabs_left", &priv->menu_tabs_left,
1929 "menu_tabs_right", &priv->menu_tabs_right,
1930 "menu_tabs_detach", &priv->menu_tabs_detach,
1934 empathy_builder_connect (gui, window,
1935 "menu_conv", "activate", chat_window_conv_activate_cb,
1936 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1937 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1938 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
1939 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1940 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1941 "menu_conv_close", "activate", chat_window_close_activate_cb,
1942 "menu_edit", "activate", chat_window_edit_activate_cb,
1943 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1944 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1945 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1946 "menu_edit_find", "activate", chat_window_find_activate_cb,
1947 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1948 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1949 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1950 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1951 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1952 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1953 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1954 "menu_help_about", "activate", chat_window_help_about_activate_cb,
1957 g_object_ref (priv->ui_manager);
1958 g_object_unref (gui);
1960 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1962 priv->notebook = gtk_notebook_new ();
1963 gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
1964 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
1965 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
1966 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1967 gtk_widget_show (priv->notebook);
1970 accel_group = gtk_accel_group_new ();
1971 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
1973 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
1974 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
1977 gtk_accel_group_connect (accel_group,
1984 g_object_unref (accel_group);
1986 /* Set up drag target lists */
1987 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
1988 G_N_ELEMENTS (drag_types_dest_contact));
1989 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1990 G_N_ELEMENTS (drag_types_dest_file));
1992 /* Set up smiley menu */
1993 smiley_manager = empathy_smiley_manager_dup_singleton ();
1994 submenu = empathy_smiley_menu_new (smiley_manager,
1995 chat_window_insert_smiley_activate_cb,
1997 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1998 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
1999 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2000 g_object_unref (smiley_manager);
2002 /* Set up signals we can't do with ui file since we may need to
2003 * block/unblock them at some later stage.
2006 g_signal_connect (priv->dialog,
2008 G_CALLBACK (chat_window_delete_event_cb),
2010 g_signal_connect (priv->dialog,
2012 G_CALLBACK (chat_window_focus_in_event_cb),
2014 g_signal_connect_after (priv->notebook,
2016 G_CALLBACK (chat_window_page_switched_cb),
2018 g_signal_connect (priv->notebook,
2020 G_CALLBACK (chat_window_page_added_cb),
2022 g_signal_connect (priv->notebook,
2024 G_CALLBACK (chat_window_page_removed_cb),
2027 /* Set up drag and drop */
2028 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2029 GTK_DEST_DEFAULT_HIGHLIGHT,
2031 G_N_ELEMENTS (drag_types_dest),
2032 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2034 /* connect_after to allow GtkNotebook's built-in tab switching */
2035 g_signal_connect_after (priv->notebook,
2037 G_CALLBACK (chat_window_drag_motion),
2039 g_signal_connect (priv->notebook,
2040 "drag-data-received",
2041 G_CALLBACK (chat_window_drag_data_received),
2043 g_signal_connect (priv->notebook,
2045 G_CALLBACK (chat_window_drag_drop),
2048 chat_windows = g_list_prepend (chat_windows, window);
2050 /* Set up private details */
2052 priv->chats_new_msg = NULL;
2053 priv->chats_composing = NULL;
2054 priv->current_chat = NULL;
2055 priv->notification = NULL;
2057 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2059 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2060 priv->chat_manager_chats_changed_id =
2061 g_signal_connect (priv->chat_manager, "chats-changed",
2062 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2065 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2066 empathy_chat_manager_get_num_chats (priv->chat_manager),
2071 empathy_chat_window_new (void)
2073 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
2076 /* Returns the window to open a new tab in if there is only one window
2077 * visble, otherwise, returns NULL indicating that a new window should
2081 empathy_chat_window_get_default (gboolean room)
2083 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2085 gboolean separate_windows = TRUE;
2087 separate_windows = g_settings_get_boolean (gsettings,
2088 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2090 g_object_unref (gsettings);
2092 if (separate_windows) {
2093 /* Always create a new window */
2097 for (l = chat_windows; l; l = l->next) {
2098 EmpathyChatWindowPriv *priv;
2099 EmpathyChatWindow *chat_window;
2102 chat_window = l->data;
2103 priv = GET_PRIV (chat_window);
2105 dialog = empathy_chat_window_get_dialog (chat_window);
2106 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
2107 guint nb_rooms, nb_private;
2108 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2110 /* Skip the window if there aren't any rooms in it */
2111 if (room && nb_rooms == 0)
2114 /* Skip the window if there aren't any 1-1 chats in it */
2115 if (!room && nb_private == 0)
2118 /* Found a visible window on this desktop */
2127 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2129 EmpathyChatWindowPriv *priv;
2131 g_return_val_if_fail (window != NULL, NULL);
2133 priv = GET_PRIV (window);
2135 return priv->dialog;
2139 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2142 EmpathyChatWindowPriv *priv;
2144 GtkWidget *popup_label;
2146 GValue value = { 0, };
2148 g_return_if_fail (window != NULL);
2149 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2151 priv = GET_PRIV (window);
2153 /* Reference the chat object */
2154 g_object_ref (chat);
2156 /* If this window has just been created, position it */
2157 if (priv->chats == NULL) {
2158 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2159 const gchar *name = "chat-window";
2160 gboolean separate_windows;
2162 separate_windows = g_settings_get_boolean (gsettings,
2163 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2165 g_object_unref (gsettings);
2167 if (separate_windows) {
2168 name = empathy_chat_get_id (chat);
2170 else if (empathy_chat_is_room (chat)) {
2171 name = "room-window";
2174 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2177 child = GTK_WIDGET (chat);
2178 label = chat_window_create_label (window, chat, TRUE);
2179 popup_label = chat_window_create_label (window, chat, FALSE);
2180 gtk_widget_show (child);
2182 g_signal_connect (chat, "notify::name",
2183 G_CALLBACK (chat_window_chat_notify_cb),
2185 g_signal_connect (chat, "notify::subject",
2186 G_CALLBACK (chat_window_chat_notify_cb),
2188 g_signal_connect (chat, "notify::remote-contact",
2189 G_CALLBACK (chat_window_chat_notify_cb),
2191 chat_window_chat_notify_cb (chat);
2193 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2194 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2195 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2196 g_value_init (&value, G_TYPE_BOOLEAN);
2197 g_value_set_boolean (&value, TRUE);
2198 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2199 child, "tab-expand" , &value);
2200 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2201 child, "tab-fill" , &value);
2202 g_value_unset (&value);
2204 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2208 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2211 EmpathyChatWindowPriv *priv;
2213 EmpathyContact *remote_contact;
2214 EmpathyChatManager *chat_manager;
2216 g_return_if_fail (window != NULL);
2217 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2219 priv = GET_PRIV (window);
2221 g_signal_handlers_disconnect_by_func (chat,
2222 chat_window_chat_notify_cb,
2224 remote_contact = g_object_get_data (G_OBJECT (chat),
2225 "chat-window-remote-contact");
2226 if (remote_contact) {
2227 g_signal_handlers_disconnect_by_func (remote_contact,
2228 chat_window_update_chat_tab,
2232 chat_manager = empathy_chat_manager_dup_singleton ();
2233 empathy_chat_manager_closed_chat (chat_manager, chat);
2234 g_object_unref (chat_manager);
2236 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2238 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2240 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2242 g_object_unref (chat);
2246 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2247 EmpathyChatWindow *new_window,
2252 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2253 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2254 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2256 widget = GTK_WIDGET (chat);
2258 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2259 G_OBJECT (widget)->ref_count);
2261 /* We reference here to make sure we don't loose the widget
2262 * and the EmpathyChat object during the move.
2264 g_object_ref (chat);
2265 g_object_ref (widget);
2267 empathy_chat_window_remove_chat (old_window, chat);
2268 empathy_chat_window_add_chat (new_window, chat);
2270 g_object_unref (widget);
2271 g_object_unref (chat);
2275 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2278 EmpathyChatWindowPriv *priv;
2281 g_return_if_fail (window != NULL);
2282 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2284 priv = GET_PRIV (window);
2286 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2288 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2293 empathy_chat_window_has_focus (EmpathyChatWindow *window)
2295 EmpathyChatWindowPriv *priv;
2298 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
2300 priv = GET_PRIV (window);
2302 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
2308 empathy_chat_window_find_chat (TpAccount *account,
2313 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2315 for (l = chat_windows; l; l = l->next) {
2316 EmpathyChatWindowPriv *priv;
2317 EmpathyChatWindow *window;
2321 priv = GET_PRIV (window);
2323 for (ll = priv->chats; ll; ll = ll->next) {
2328 if (account == empathy_chat_get_account (chat) &&
2329 !tp_strdiff (id, empathy_chat_get_id (chat))) {
2339 empathy_chat_window_present_chat (EmpathyChat *chat,
2342 EmpathyChatWindow *window;
2343 EmpathyChatWindowPriv *priv;
2344 guint32 x_timestamp;
2346 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2348 window = chat_window_find_chat (chat);
2350 /* If the chat has no window, create one */
2351 if (window == NULL) {
2352 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2354 window = empathy_chat_window_new ();
2355 gtk_widget_show_all (GET_PRIV (window)->dialog);
2358 empathy_chat_window_add_chat (window, chat);
2361 /* Don't force the window to show itself when it wasn't
2362 * an action by the user
2364 if (timestamp == EMPATHY_DISPATCHER_NON_USER_ACTION)
2367 priv = GET_PRIV (window);
2369 if (timestamp == EMPATHY_DISPATCHER_CURRENT_TIME) {
2370 x_timestamp = GDK_CURRENT_TIME;
2372 x_timestamp = CLAMP (timestamp, 0, G_MAXUINT32);
2373 /* Don't present or switch tab if the action was earlier than the
2374 * last actions X time, accounting for overflow and the first ever
2377 if (priv->x_user_action_time != 0
2378 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2381 priv->x_user_action_time = x_timestamp;
2384 empathy_chat_window_switch_to_chat (window, chat);
2385 empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2388 gtk_widget_grab_focus (chat->input_text_view);
2392 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2396 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2398 guint _nb_rooms = 0, _nb_private = 0;
2400 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2401 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2407 if (nb_rooms != NULL)
2408 *nb_rooms = _nb_rooms;
2409 if (nb_private != NULL)
2410 *nb_private = _nb_private;