2 * Copyright (C) 2003-2007 Imendio AB
3 * Copyright (C) 2007-2012 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Richard Hult <richard@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24 * Xavier Claessens <xclaesse@gmail.com>
25 * Rômulo Fernandes Machado <romulo@castorgroup.net>
29 #include "empathy-chat-window.h"
31 #include <glib/gi18n.h>
32 #include <tp-account-widgets/tpaw-builder.h>
33 #include <tp-account-widgets/tpaw-utils.h>
35 #include "empathy-about-dialog.h"
36 #include "empathy-chat-manager.h"
37 #include "empathy-chatroom-manager.h"
38 #include "empathy-client-factory.h"
39 #include "empathy-geometry.h"
40 #include "empathy-gsettings.h"
41 #include "empathy-images.h"
42 #include "empathy-invite-participant-dialog.h"
43 #include "empathy-notify-manager.h"
44 #include "empathy-request-util.h"
45 #include "empathy-sound-manager.h"
46 #include "empathy-ui-utils.h"
47 #include "empathy-utils.h"
48 #include "empathy-new-message-dialog.h"
50 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
51 #include "empathy-debug.h"
53 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
55 #define X_EARLIER_OR_EQL(t1, t2) \
56 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
57 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
62 PROP_INDIVIDUAL_MGR = 1
65 struct _EmpathyChatWindowPriv
67 EmpathyChat *current_chat;
70 gboolean dnd_same_window;
71 EmpathyChatroomManager *chatroom_manager;
72 EmpathyNotifyManager *notify_mgr;
73 EmpathyIndividualManager *individual_mgr;
75 NotifyNotification *notification;
77 GtkTargetList *contact_targets;
78 GtkTargetList *file_targets;
80 EmpathyChatManager *chat_manager;
81 gulong chat_manager_chats_changed_id;
84 GtkUIManager *ui_manager;
85 GtkAction *menu_conv_insert_smiley;
86 GtkAction *menu_conv_favorite;
87 GtkAction *menu_conv_join_chat;
88 GtkAction *menu_conv_leave_chat;
89 GtkAction *menu_conv_always_urgent;
90 GtkAction *menu_conv_toggle_contacts;
92 GtkAction *menu_edit_cut;
93 GtkAction *menu_edit_copy;
94 GtkAction *menu_edit_paste;
95 GtkAction *menu_edit_find;
97 GtkAction *menu_tabs_next;
98 GtkAction *menu_tabs_prev;
99 GtkAction *menu_tabs_undo_close_tab;
100 GtkAction *menu_tabs_left;
101 GtkAction *menu_tabs_right;
102 GtkAction *menu_tabs_detach;
104 /* Last user action time we acted upon to show a tab */
105 guint32 x_user_action_time;
107 GSettings *gsettings_chat;
108 GSettings *gsettings_notif;
109 GSettings *gsettings_ui;
111 EmpathySoundManager *sound_mgr;
113 gboolean updating_menu;
116 static GList *chat_windows = NULL;
118 static const guint tab_accel_keys[] =
120 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
121 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
126 DND_DRAG_TYPE_CONTACT_ID,
127 DND_DRAG_TYPE_INDIVIDUAL_ID,
128 DND_DRAG_TYPE_URI_LIST,
132 static const GtkTargetEntry drag_types_dest[] =
134 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
135 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
136 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
137 /* FIXME: disabled because of bug #640513
138 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
139 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] =
145 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
146 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
149 static const GtkTargetEntry drag_types_dest_file[] =
151 /* must be first to be prioritized, in order to receive the
152 * note's file path from Tomboy instead of an URI */
153 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
154 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
157 static void chat_window_update (EmpathyChatWindow *window,
158 gboolean update_contact_menu);
160 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
163 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
166 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
167 EmpathyChatWindow *new_window,
170 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
174 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_BIN)
177 chat_window_accel_cb (GtkAccelGroup *accelgroup,
181 EmpathyChatWindow *self)
186 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
188 if (tab_accel_keys[i] == key)
196 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
199 static EmpathyChatWindow *
200 chat_window_find_chat (EmpathyChat *chat)
204 for (l = chat_windows; l; l = l->next)
206 EmpathyChatWindow *window = l->data;
208 ll = g_list_find (window->priv->chats, chat);
217 remove_all_chats (EmpathyChatWindow *self)
221 while (self->priv->chats)
222 empathy_chat_window_remove_chat (self, self->priv->chats->data);
224 g_object_unref (self);
228 confirm_close_response_cb (GtkWidget *dialog,
230 EmpathyChatWindow *window)
234 chat = g_object_get_data (G_OBJECT (dialog), "chat");
236 gtk_widget_destroy (dialog);
238 if (response != GTK_RESPONSE_ACCEPT)
242 empathy_chat_window_remove_chat (window, chat);
244 remove_all_chats (window);
248 confirm_close (EmpathyChatWindow *self,
249 gboolean close_window,
254 gchar *primary, *secondary;
256 g_return_if_fail (n_rooms > 0);
259 g_return_if_fail (chat == NULL);
261 g_return_if_fail (chat != NULL);
263 /* If there are no chats in this window, how could we possibly have got
266 g_return_if_fail (self->priv->chats != NULL);
268 /* Treat closing a window which only has one tab exactly like closing
271 if (close_window && self->priv->chats->next == NULL)
273 close_window = FALSE;
274 chat = self->priv->chats->data;
279 primary = g_strdup (_("Close this window?"));
283 gchar *chat_name = empathy_chat_dup_name (chat);
284 secondary = g_strdup_printf (
285 _("Closing this window will leave %s. You will "
286 "not receive any further messages until you "
293 secondary = g_strdup_printf (
294 /* Note to translators: the number of chats will
295 * always be at least 2.
298 "Closing this window will leave a chat room. You will "
299 "not receive any further messages until you rejoin it.",
300 "Closing this window will leave %u chat rooms. You will "
301 "not receive any further messages until you rejoin them.",
308 gchar *chat_name = empathy_chat_dup_name (chat);
309 primary = g_strdup_printf (_("Leave %s?"), chat_name);
310 secondary = g_strdup (
311 _("You will not receive any further messages from this chat "
312 "room until you rejoin it."));
316 dialog = gtk_message_dialog_new (
317 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))),
318 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
323 gtk_window_set_title (GTK_WINDOW (dialog), "");
324 g_object_set (dialog, "secondary-text", secondary, NULL);
329 gtk_dialog_add_button (GTK_DIALOG (dialog),
330 close_window ? _("Close window") : _("Leave room"),
331 GTK_RESPONSE_ACCEPT);
332 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
333 GTK_RESPONSE_ACCEPT);
336 g_object_set_data (G_OBJECT (dialog), "chat", chat);
338 g_signal_connect (dialog, "response",
339 G_CALLBACK (confirm_close_response_cb), self);
341 gtk_window_present (GTK_WINDOW (dialog));
344 /* Returns TRUE if we should check if the user really wants to leave. If it's
345 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
346 * the user is actually in the room as opposed to having been kicked or gone
347 * offline or something), then we should check.
350 chat_needs_close_confirmation (EmpathyChat *chat)
352 return (empathy_chat_is_room (chat) &&
353 empathy_chat_get_tp_chat (chat) != NULL);
357 maybe_close_chat (EmpathyChatWindow *window,
360 g_return_if_fail (chat != NULL);
362 if (chat_needs_close_confirmation (chat))
363 confirm_close (window, FALSE, 1, chat);
365 empathy_chat_window_remove_chat (window, chat);
369 chat_window_close_clicked_cb (GtkAction *action,
372 EmpathyChatWindow *window;
374 window = chat_window_find_chat (chat);
375 maybe_close_chat (window, chat);
379 chat_tab_style_updated_cb (GtkWidget *hbox,
383 int char_width, h, w;
384 PangoContext *context;
385 PangoFontDescription *font_desc;
386 PangoFontMetrics *metrics;
388 button = g_object_get_data (G_OBJECT (user_data),
389 "chat-window-tab-close-button");
390 context = gtk_widget_get_pango_context (hbox);
392 gtk_style_context_get (gtk_widget_get_style_context (hbox),
393 GTK_STATE_FLAG_NORMAL,
397 metrics = pango_context_get_metrics (context, font_desc,
398 pango_context_get_language (context));
399 char_width = pango_font_metrics_get_approximate_char_width (metrics);
400 pango_font_metrics_unref (metrics);
402 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
403 GTK_ICON_SIZE_MENU, &w, &h);
405 /* Request at least about 12 chars width plus at least space for the status
406 * image and the close button */
407 gtk_widget_set_size_request (hbox,
408 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
410 gtk_widget_set_size_request (button, w, h);
411 pango_font_description_free (font_desc);
415 create_close_button (void)
417 GtkWidget *button, *image;
418 GtkStyleContext *context;
420 button = gtk_button_new ();
422 context = gtk_widget_get_style_context (button);
423 gtk_style_context_add_class (context, "empathy-tab-close-button");
425 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
426 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
428 /* We don't want focus/keynav for the button to avoid clutter, and
429 * Ctrl-W works anyway.
431 gtk_widget_set_can_focus (button, FALSE);
432 gtk_widget_set_can_default (button, FALSE);
434 image = gtk_image_new_from_icon_name ("window-close-symbolic",
436 gtk_widget_show (image);
438 gtk_container_add (GTK_CONTAINER (button), image);
444 chat_window_create_label (EmpathyChatWindow *window,
446 gboolean is_tab_label)
449 GtkWidget *name_label;
450 GtkWidget *status_image;
451 GtkWidget *event_box;
452 GtkWidget *event_box_hbox;
453 PangoAttrList *attr_list;
454 PangoAttribute *attr;
456 /* The spacing between the button and the label. */
457 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
459 event_box = gtk_event_box_new ();
460 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
462 name_label = gtk_label_new (NULL);
464 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
466 attr_list = pango_attr_list_new ();
467 attr = pango_attr_scale_new (1/1.2);
468 attr->start_index = 0;
469 attr->end_index = -1;
470 pango_attr_list_insert (attr_list, attr);
471 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
472 pango_attr_list_unref (attr_list);
474 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
475 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
476 g_object_set_data (G_OBJECT (chat),
477 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
480 status_image = gtk_image_new ();
482 /* Spacing between the icon and label. */
483 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
485 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
486 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
488 g_object_set_data (G_OBJECT (chat),
489 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
491 g_object_set_data (G_OBJECT (chat),
492 is_tab_label ? "chat-window-tab-tooltip-widget" :
493 "chat-window-menu-tooltip-widget",
496 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
497 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
501 GtkWidget *close_button;
502 GtkWidget *sending_spinner;
504 sending_spinner = gtk_spinner_new ();
506 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
508 g_object_set_data (G_OBJECT (chat),
509 "chat-window-tab-sending-spinner",
512 close_button = create_close_button ();
513 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
516 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
518 g_signal_connect (close_button,
520 G_CALLBACK (chat_window_close_clicked_cb), chat);
522 /* React to theme changes and also setup the size correctly. */
523 g_signal_connect (hbox, "style-updated",
524 G_CALLBACK (chat_tab_style_updated_cb), chat);
527 gtk_widget_show_all (hbox);
533 _submenu_notify_visible_changed_cb (GObject *object,
537 g_signal_handlers_disconnect_by_func (object,
538 _submenu_notify_visible_changed_cb, userdata);
540 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
544 chat_window_menu_context_update (EmpathyChatWindow *self,
549 gboolean wrap_around;
550 gboolean is_connected;
553 page_num = gtk_notebook_get_current_page (
554 GTK_NOTEBOOK (self->priv->notebook));
555 first_page = (page_num == 0);
556 last_page = (page_num == (num_pages - 1));
557 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
559 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
561 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
563 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
565 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
566 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
567 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
568 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
572 chat_window_conversation_menu_update (EmpathyChatWindow *self)
574 EmpathyTpChat *tp_chat;
575 TpConnection *connection;
577 gboolean sensitive = FALSE;
579 g_return_if_fail (self->priv->current_chat != NULL);
581 action = gtk_ui_manager_get_action (self->priv->ui_manager,
582 "/chats_menubar/menu_conv/menu_conv_invite_participant");
583 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
587 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
589 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
590 (tp_connection_get_status (connection, NULL) ==
591 TP_CONNECTION_STATUS_CONNECTED);
594 gtk_action_set_sensitive (action, sensitive);
598 chat_window_contact_menu_update (EmpathyChatWindow *self)
600 GtkWidget *menu, *submenu, *orig_submenu;
602 if (self->priv->current_chat == NULL)
605 if (self->priv->updating_menu)
607 self->priv->updating_menu = TRUE;
609 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
610 "/chats_menubar/menu_contact");
611 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
613 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
615 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
619 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
620 g_object_set_data (G_OBJECT (submenu), "window", self);
622 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
623 gtk_widget_show (menu);
624 gtk_widget_set_sensitive (menu, TRUE);
628 gtk_widget_set_sensitive (menu, FALSE);
633 tp_g_signal_connect_object (orig_submenu,
635 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
638 self->priv->updating_menu = FALSE;
642 get_all_unread_messages (EmpathyChatWindow *self)
647 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
648 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
654 get_window_title_name (EmpathyChatWindow *self)
656 gchar *active_name, *ret;
658 guint current_unread_msgs;
660 nb_chats = g_list_length (self->priv->chats);
661 g_assert (nb_chats > 0);
663 active_name = empathy_chat_dup_name (self->priv->current_chat);
665 current_unread_msgs = empathy_chat_get_nb_unread_messages (
666 self->priv->current_chat);
671 if (current_unread_msgs == 0)
672 ret = g_strdup (active_name);
674 ret = g_strdup_printf (ngettext (
676 "%s (%d unread)", current_unread_msgs),
677 active_name, current_unread_msgs);
681 guint nb_others = nb_chats - 1;
682 guint all_unread_msgs;
684 all_unread_msgs = get_all_unread_messages (self);
686 if (all_unread_msgs == 0)
688 /* no unread message */
689 ret = g_strdup_printf (ngettext (
691 "%s (and %u others)", nb_others),
692 active_name, nb_others);
694 else if (all_unread_msgs == current_unread_msgs)
696 /* unread messages are in the current tab */
697 ret = g_strdup_printf (ngettext (
699 "%s (%d unread)", current_unread_msgs),
700 active_name, current_unread_msgs);
702 else if (current_unread_msgs == 0)
704 /* unread messages are in other tabs */
705 ret = g_strdup_printf (ngettext (
706 "%s (%d unread from others)",
707 "%s (%d unread from others)",
709 active_name, all_unread_msgs);
713 /* unread messages are in all the tabs */
714 ret = g_strdup_printf (ngettext (
715 "%s (%d unread from all)",
716 "%s (%d unread from all)",
718 active_name, all_unread_msgs);
722 g_free (active_name);
728 chat_window_title_update (EmpathyChatWindow *self)
732 name = get_window_title_name (self);
733 //gtk_window_set_title (GTK_WINDOW (self), name);
738 chat_window_icon_update (EmpathyChatWindow *self,
739 gboolean new_messages)
742 EmpathyContact *remote_contact;
743 gboolean avatar_in_icon;
746 n_chats = g_list_length (self->priv->chats);
748 /* Update window icon */
751 //gtk_window_set_icon_name (GTK_WINDOW (self),
752 // EMPATHY_IMAGE_MESSAGE);
756 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
757 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
759 if (n_chats == 1 && avatar_in_icon)
761 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
762 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
764 //gtk_window_set_icon (GTK_WINDOW (self), icon);
767 g_object_unref (icon);
771 //gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
777 chat_window_close_button_update (EmpathyChatWindow *self,
781 GtkWidget *chat_close_button;
786 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
787 chat_close_button = g_object_get_data (G_OBJECT (chat),
788 "chat-window-tab-close-button");
789 gtk_widget_hide (chat_close_button);
793 for (i=0; i<num_pages; i++)
795 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
796 chat_close_button = g_object_get_data (G_OBJECT (chat),
797 "chat-window-tab-close-button");
798 gtk_widget_show (chat_close_button);
804 chat_window_update (EmpathyChatWindow *self,
805 gboolean update_contact_menu)
809 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
811 /* Update Tab menu */
812 chat_window_menu_context_update (self, num_pages);
814 chat_window_conversation_menu_update (self);
816 /* If this update is due to a focus-in event, we know the menu will be
817 the same as when we last left it, so no work to do. Besides, if we
818 swap out the menu on a focus-in, we may confuse any external global
820 if (update_contact_menu)
822 chat_window_contact_menu_update (self);
825 chat_window_title_update (self);
827 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
829 chat_window_close_button_update (self, num_pages);
833 append_markup_printf (GString *string,
840 va_start (args, format);
842 tmp = g_markup_vprintf_escaped (format, args);
843 g_string_append (string, tmp);
850 chat_window_update_chat_tab_full (EmpathyChat *chat,
851 gboolean update_contact_menu)
853 EmpathyChatWindow *self;
854 EmpathyContact *remote_contact;
858 const gchar *subject;
859 const gchar *status = NULL;
863 const gchar *icon_name;
864 GtkWidget *tab_image;
865 GtkWidget *menu_image;
866 GtkWidget *sending_spinner;
869 self = chat_window_find_chat (chat);
873 /* Get information */
874 name = empathy_chat_dup_name (chat);
875 account = empathy_chat_get_account (chat);
876 subject = empathy_chat_get_subject (chat);
877 remote_contact = empathy_chat_get_remote_contact (chat);
879 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
881 name, tp_proxy_get_object_path (account), subject, remote_contact);
883 /* Update tab image */
884 if (empathy_chat_get_tp_chat (chat) == NULL)
886 /* No TpChat, we are disconnected */
889 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
891 icon_name = EMPATHY_IMAGE_MESSAGE;
893 else if (remote_contact && empathy_chat_is_composing (chat))
895 icon_name = EMPATHY_IMAGE_TYPING;
897 else if (empathy_chat_is_sms_channel (chat))
899 icon_name = EMPATHY_IMAGE_SMS;
901 else if (remote_contact)
903 icon_name = empathy_icon_name_for_contact (remote_contact);
907 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
910 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
911 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
913 if (icon_name != NULL)
915 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
917 gtk_widget_show (tab_image);
918 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
920 gtk_widget_show (menu_image);
924 gtk_widget_hide (tab_image);
925 gtk_widget_hide (menu_image);
928 /* Update the sending spinner */
929 nb_sending = empathy_chat_get_n_messages_sending (chat);
930 sending_spinner = g_object_get_data (G_OBJECT (chat),
931 "chat-window-tab-sending-spinner");
933 g_object_set (sending_spinner,
934 "active", nb_sending > 0,
935 "visible", nb_sending > 0,
938 /* Update tab tooltip */
939 tooltip = g_string_new (NULL);
943 id = empathy_contact_get_id (remote_contact);
944 status = empathy_contact_get_presence_message (remote_contact);
951 if (empathy_chat_is_sms_channel (chat))
952 append_markup_printf (tooltip, "%s ", _("SMS:"));
954 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
955 id, tp_account_get_display_name (account));
959 char *tmp = g_strdup_printf (
960 ngettext ("Sending %d message",
961 "Sending %d messages",
965 g_string_append (tooltip, "\n");
966 g_string_append (tooltip, tmp);
968 gtk_widget_set_tooltip_text (sending_spinner, tmp);
972 if (!TPAW_STR_EMPTY (status))
973 append_markup_printf (tooltip, "\n<i>%s</i>", status);
975 if (!TPAW_STR_EMPTY (subject))
976 append_markup_printf (tooltip, "\n<b>%s</b> %s",
977 _("Topic:"), subject);
979 if (remote_contact && empathy_chat_is_composing (chat))
980 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
982 if (remote_contact != NULL)
984 const gchar * const *types;
986 types = empathy_contact_get_client_types (remote_contact);
987 if (empathy_client_types_contains_mobile_device ((GStrv) types))
989 /* I'm on a mobile device ! */
992 name = g_strdup_printf ("☎ %s", name);
997 markup = g_string_free (tooltip, FALSE);
998 widget = g_object_get_data (G_OBJECT (chat),
999 "chat-window-tab-tooltip-widget");
1000 gtk_widget_set_tooltip_markup (widget, markup);
1002 widget = g_object_get_data (G_OBJECT (chat),
1003 "chat-window-menu-tooltip-widget");
1004 gtk_widget_set_tooltip_markup (widget, markup);
1007 /* Update tab and menu label */
1008 if (empathy_chat_is_highlighted (chat))
1010 markup = g_markup_printf_escaped (
1011 "<span color=\"red\" weight=\"bold\">%s</span>",
1016 markup = g_markup_escape_text (name, -1);
1019 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1020 gtk_label_set_markup (GTK_LABEL (widget), markup);
1021 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1022 gtk_label_set_markup (GTK_LABEL (widget), markup);
1025 /* Update the window if it's the current chat */
1026 if (self->priv->current_chat == chat)
1027 chat_window_update (self, update_contact_menu);
1033 chat_window_update_chat_tab (EmpathyChat *chat)
1035 chat_window_update_chat_tab_full (chat, TRUE);
1039 chat_window_chat_notify_cb (EmpathyChat *chat)
1041 EmpathyChatWindow *window;
1042 EmpathyContact *old_remote_contact;
1043 EmpathyContact *remote_contact = NULL;
1045 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1046 "chat-window-remote-contact");
1047 remote_contact = empathy_chat_get_remote_contact (chat);
1049 if (old_remote_contact != remote_contact)
1051 /* The remote-contact associated with the chat changed, we need
1052 * to keep track of any change of that contact and update the
1053 * window each time. */
1055 g_signal_connect_swapped (remote_contact, "notify",
1056 G_CALLBACK (chat_window_update_chat_tab), chat);
1058 if (old_remote_contact)
1059 g_signal_handlers_disconnect_by_func (old_remote_contact,
1060 chat_window_update_chat_tab, chat);
1062 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1063 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1066 chat_window_update_chat_tab (chat);
1068 window = chat_window_find_chat (chat);
1070 chat_window_update (window, FALSE);
1074 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1075 EmpathySmiley *smiley,
1078 EmpathyChatWindow *self = user_data;
1080 GtkTextBuffer *buffer;
1082 chat = self->priv->current_chat;
1083 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1085 empathy_chat_insert_smiley (buffer, smiley);
1089 chat_window_conv_activate_cb (GtkAction *action,
1090 EmpathyChatWindow *self)
1094 EmpathyContact *remote_contact = NULL;
1095 gboolean disconnected;
1097 /* Favorite room menu */
1098 is_room = empathy_chat_is_room (self->priv->current_chat);
1103 gboolean found = FALSE;
1104 EmpathyChatroom *chatroom;
1106 room = empathy_chat_get_id (self->priv->current_chat);
1107 account = empathy_chat_get_account (self->priv->current_chat);
1108 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1111 if (chatroom != NULL)
1112 found = empathy_chatroom_is_favorite (chatroom);
1114 DEBUG ("This room %s favorite", found ? "is" : "is not");
1115 gtk_toggle_action_set_active (
1116 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1118 if (chatroom != NULL)
1119 found = empathy_chatroom_is_always_urgent (chatroom);
1121 gtk_toggle_action_set_active (
1122 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1125 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1126 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1128 /* Show contacts menu */
1129 g_object_get (self->priv->current_chat,
1130 "remote-contact", &remote_contact,
1131 "show-contacts", &active,
1134 if (remote_contact == NULL)
1136 gtk_toggle_action_set_active (
1137 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1140 /* Menu-items to be visible for MUCs only */
1141 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1142 (remote_contact == NULL));
1144 disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1147 gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1148 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1152 TpChannel *channel = NULL;
1153 TpContact *self_contact = NULL;
1154 TpHandle self_handle = 0;
1156 channel = (TpChannel *) (empathy_chat_get_tp_chat (
1157 self->priv->current_chat));
1158 self_contact = tp_channel_group_get_self_contact (channel);
1159 if (self_contact == NULL)
1161 /* The channel may not be a group */
1162 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1166 self_handle = tp_contact_get_handle (self_contact);
1167 /* There is sometimes a lag between the members-changed signal
1168 emitted on tp-chat and invalidated signal being emitted on the channel.
1169 Leave Chat menu-item should be sensitive only till our self-handle is
1170 a part of channel-members */
1171 gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1175 /* Join Chat is insensitive for a connected chat */
1176 gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1179 if (remote_contact != NULL)
1180 g_object_unref (remote_contact);
1184 chat_window_clear_activate_cb (GtkAction *action,
1185 EmpathyChatWindow *self)
1187 empathy_chat_clear (self->priv->current_chat);
1191 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1192 EmpathyChatWindow *self)
1198 EmpathyChatroom *chatroom;
1200 active = gtk_toggle_action_get_active (toggle_action);
1201 account = empathy_chat_get_account (self->priv->current_chat);
1202 room = empathy_chat_get_id (self->priv->current_chat);
1203 name = empathy_chat_dup_name (self->priv->current_chat);
1205 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1206 account, room, name);
1208 empathy_chatroom_set_favorite (chatroom, active);
1209 g_object_unref (chatroom);
1214 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1215 EmpathyChatWindow *self)
1221 EmpathyChatroom *chatroom;
1223 active = gtk_toggle_action_get_active (toggle_action);
1224 account = empathy_chat_get_account (self->priv->current_chat);
1225 room = empathy_chat_get_id (self->priv->current_chat);
1226 name = empathy_chat_dup_name (self->priv->current_chat);
1228 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1229 account, room, name);
1231 empathy_chatroom_set_always_urgent (chatroom, active);
1232 g_object_unref (chatroom);
1237 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1238 EmpathyChatWindow *self)
1242 active = gtk_toggle_action_get_active (toggle_action);
1244 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1248 chat_window_invite_participant_activate_cb (GtkAction *action,
1249 EmpathyChatWindow *self)
1252 EmpathyTpChat *tp_chat;
1255 g_return_if_fail (self->priv->current_chat != NULL);
1257 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1259 dialog = empathy_invite_participant_dialog_new (
1260 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), tp_chat);
1262 gtk_widget_show (dialog);
1264 response = gtk_dialog_run (GTK_DIALOG (dialog));
1266 if (response == GTK_RESPONSE_ACCEPT)
1268 TpContact *tp_contact;
1269 EmpathyContact *contact;
1271 tp_contact = empathy_invite_participant_dialog_get_selected (
1272 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1273 if (tp_contact == NULL)
1276 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1278 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1280 g_object_unref (contact);
1284 gtk_widget_destroy (dialog);
1288 chat_window_join_chat_activate_cb (GtkAction *action,
1289 EmpathyChatWindow *self)
1291 g_return_if_fail (self->priv->current_chat != NULL);
1293 empathy_chat_join_muc (self->priv->current_chat,
1294 empathy_chat_get_id (self->priv->current_chat));
1298 chat_window_leave_chat_activate_cb (GtkAction *action,
1299 EmpathyChatWindow *self)
1301 EmpathyTpChat * tp_chat;
1303 g_return_if_fail (self->priv->current_chat != NULL);
1305 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1306 if (tp_chat != NULL)
1307 empathy_tp_chat_leave (tp_chat, "");
1311 chat_window_close_activate_cb (GtkAction *action,
1312 EmpathyChatWindow *self)
1314 g_return_if_fail (self->priv->current_chat != NULL);
1316 maybe_close_chat (self, self->priv->current_chat);
1320 chat_window_edit_activate_cb (GtkAction *action,
1321 EmpathyChatWindow *self)
1323 GtkClipboard *clipboard;
1324 GtkTextBuffer *buffer;
1325 gboolean text_available;
1327 g_return_if_fail (self->priv->current_chat != NULL);
1329 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1331 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1332 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1333 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1337 buffer = gtk_text_view_get_buffer (
1338 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1340 if (gtk_text_buffer_get_has_selection (buffer))
1342 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1343 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1349 selection = empathy_theme_adium_get_has_selection (
1350 self->priv->current_chat->view);
1352 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1353 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1356 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1357 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1358 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1362 chat_window_cut_activate_cb (GtkAction *action,
1363 EmpathyChatWindow *self)
1365 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1367 empathy_chat_cut (self->priv->current_chat);
1371 chat_window_copy_activate_cb (GtkAction *action,
1372 EmpathyChatWindow *self)
1374 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1376 empathy_chat_copy (self->priv->current_chat);
1380 chat_window_paste_activate_cb (GtkAction *action,
1381 EmpathyChatWindow *self)
1383 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1385 empathy_chat_paste (self->priv->current_chat);
1389 chat_window_find_activate_cb (GtkAction *action,
1390 EmpathyChatWindow *self)
1392 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1394 empathy_chat_find (self->priv->current_chat);
1398 chat_window_tabs_next_activate_cb (GtkAction *action,
1399 EmpathyChatWindow *self)
1401 gint index_, numPages;
1402 gboolean wrap_around;
1404 g_object_get (gtk_settings_get_default (),
1405 "gtk-keynav-wrap-around", &wrap_around,
1408 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1409 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1411 if (index_ == (numPages - 1) && wrap_around)
1413 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1417 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1421 chat_window_tabs_previous_activate_cb (GtkAction *action,
1422 EmpathyChatWindow *self)
1424 gint index_, numPages;
1425 gboolean wrap_around;
1427 g_object_get (gtk_settings_get_default (),
1428 "gtk-keynav-wrap-around", &wrap_around,
1431 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1432 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1434 if (index_ <= 0 && wrap_around)
1436 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1441 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1445 empathy_chat_window_next_tab (EmpathyChatWindow *self)
1447 chat_window_tabs_next_activate_cb (NULL, self);
1451 empathy_chat_window_prev_tab (EmpathyChatWindow *self)
1453 chat_window_tabs_previous_activate_cb (NULL, self);
1458 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1459 EmpathyChatWindow *self)
1461 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1462 empathy_get_current_action_time ());
1466 chat_window_tabs_left_activate_cb (GtkAction *action,
1467 EmpathyChatWindow *self)
1470 gint index_, num_pages;
1472 chat = self->priv->current_chat;
1473 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1477 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1480 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1481 chat_window_menu_context_update (self, num_pages);
1485 chat_window_tabs_right_activate_cb (GtkAction *action,
1486 EmpathyChatWindow *self)
1489 gint index_, num_pages;
1491 chat = self->priv->current_chat;
1492 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1494 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1497 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1498 chat_window_menu_context_update (self, num_pages);
1502 empathy_chat_window_new (void)
1504 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1509 chat_window_detach_activate_cb (GtkAction *action,
1510 EmpathyChatWindow *self)
1512 EmpathyChatWindow *new_window;
1515 chat = self->priv->current_chat;
1516 new_window = empathy_chat_window_new ();
1518 empathy_chat_window_move_chat (self, new_window, chat);
1520 gtk_widget_show (GTK_WIDGET (new_window));
1524 chat_window_help_contents_activate_cb (GtkAction *action,
1525 EmpathyChatWindow *self)
1527 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1531 chat_window_help_about_activate_cb (GtkAction *action,
1532 EmpathyChatWindow *self)
1534 empathy_about_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
1538 chat_window_delete_event_cb (GtkWidget *dialog,
1540 EmpathyChatWindow *self)
1542 EmpathyChat *chat = NULL;
1546 DEBUG ("Delete event received");
1548 for (l = self->priv->chats; l != NULL; l = l->next)
1550 if (chat_needs_close_confirmation (l->data))
1559 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1563 remove_all_chats (self);
1570 chat_window_composing_cb (EmpathyChat *chat,
1571 gboolean is_composing,
1572 EmpathyChatWindow *self)
1574 chat_window_update_chat_tab (chat);
1578 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1581 //gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1585 chat_window_notification_closed_cb (NotifyNotification *notify,
1586 EmpathyChatWindow *self)
1588 g_object_unref (notify);
1589 if (self->priv->notification == notify)
1590 self->priv->notification = NULL;
1594 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1595 EmpathyMessage *message,
1598 EmpathyContact *sender;
1599 const gchar *header;
1603 gboolean res, has_x_canonical_append;
1604 NotifyNotification *notification = self->priv->notification;
1606 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1609 res = g_settings_get_boolean (self->priv->gsettings_notif,
1610 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1615 sender = empathy_message_get_sender (message);
1616 header = empathy_contact_get_alias (sender);
1617 body = empathy_message_get_body (message);
1618 escaped = g_markup_escape_text (body, -1);
1620 has_x_canonical_append = empathy_notify_manager_has_capability (
1621 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1623 if (notification != NULL && !has_x_canonical_append)
1625 /* if the notification server supports x-canonical-append, it is
1626 better to not use notify_notification_update to avoid
1627 overwriting the current notification message */
1628 notify_notification_update (notification,
1629 header, escaped, NULL);
1633 /* if the notification server supports x-canonical-append,
1634 the hint will be added, so that the message from the
1635 just created notification will be automatically appended
1636 to an existing notification with the same title.
1637 In this way the previous message will not be lost: the new
1638 message will appear below it, in the same notification */
1639 const gchar *category = empathy_chat_is_room (chat)
1640 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1641 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1643 notification = empathy_notify_manager_create_notification (header,
1646 if (self->priv->notification == NULL)
1647 self->priv->notification = notification;
1649 tp_g_signal_connect_object (notification, "closed",
1650 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1652 if (has_x_canonical_append)
1654 /* We have to set a not empty string to keep libnotify happy */
1655 notify_notification_set_hint_string (notification,
1656 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1659 notify_notification_set_hint (notification,
1660 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1663 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1664 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1668 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1669 g_object_unref (pixbuf);
1672 notify_notification_show (notification, NULL);
1678 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1682 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1684 g_object_get ( gtk_widget_get_toplevel (GTK_WIDGET (self)), "has-toplevel-focus", &has_focus, NULL);
1690 chat_window_new_message_cb (EmpathyChat *chat,
1691 EmpathyMessage *message,
1693 gboolean should_highlight,
1694 EmpathyChatWindow *self)
1697 gboolean needs_urgency;
1698 EmpathyContact *sender;
1700 has_focus = empathy_chat_window_has_focus (self);
1702 /* - if we're the sender, we play the sound if it's specified in the
1703 * preferences and we're not away.
1704 * - if we receive a message, we play the sound if it's specified in the
1705 * preferences and the window does not have focus on the chat receiving
1709 sender = empathy_message_get_sender (message);
1711 if (empathy_contact_is_user (sender))
1713 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1714 EMPATHY_SOUND_MESSAGE_OUTGOING);
1718 if (has_focus && self->priv->current_chat == chat)
1720 /* window and tab are focused so consider the message to be read */
1722 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1723 empathy_chat_messages_read (chat);
1727 /* Update the chat tab if this is the first unread message */
1728 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1730 chat_window_update_chat_tab (chat);
1733 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1734 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1735 * an unamed MUC (msn-like).
1736 * In case of a MUC, we set urgency if either:
1737 * a) the chatroom's always_urgent property is TRUE
1738 * b) the message contains our alias
1740 if (empathy_chat_is_room (chat))
1744 EmpathyChatroom *chatroom;
1746 account = empathy_chat_get_account (chat);
1747 room = empathy_chat_get_id (chat);
1749 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1752 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1753 needs_urgency = TRUE;
1755 needs_urgency = should_highlight;
1759 needs_urgency = TRUE;
1765 chat_window_set_urgency_hint (self, TRUE);
1767 /* Pending messages have already been displayed and notified in the
1768 * approver, so we don't display a notification and play a sound
1772 empathy_sound_manager_play (self->priv->sound_mgr,
1773 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1775 chat_window_show_or_update_notification (self, message, chat);
1779 /* update the number of unread messages and the window icon */
1780 chat_window_title_update (self);
1781 chat_window_icon_update (self, TRUE);
1785 chat_window_command_part (EmpathyChat *chat,
1788 EmpathyChat *chat_to_be_parted;
1789 EmpathyTpChat *tp_chat = NULL;
1791 if (strv[1] == NULL)
1793 /* No chatroom ID specified */
1794 tp_chat = empathy_chat_get_tp_chat (chat);
1797 empathy_tp_chat_leave (tp_chat, "");
1802 chat_to_be_parted = empathy_chat_window_find_chat (
1803 empathy_chat_get_account (chat), strv[1], FALSE);
1805 if (chat_to_be_parted != NULL)
1807 /* Found a chatroom matching the specified ID */
1808 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1811 empathy_tp_chat_leave (tp_chat, strv[2]);
1817 /* Going by the syntax of PART command:
1819 * /PART [<chatroom-ID>] [<reason>]
1821 * Chatroom-ID is not a must to specify a reason.
1822 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1823 * MUC then the current chatroom should be parted and srtv[1] should
1824 * be treated as part of the optional part-message. */
1825 message = g_strconcat (strv[1], " ", strv[2], NULL);
1826 tp_chat = empathy_chat_get_tp_chat (chat);
1829 empathy_tp_chat_leave (tp_chat, message);
1835 static GtkNotebook *
1836 notebook_create_window_cb (GtkNotebook *source,
1842 EmpathyChatWindow *window, *new_window;
1845 chat = EMPATHY_CHAT (page);
1846 window = chat_window_find_chat (chat);
1848 new_window = empathy_chat_window_new ();
1850 DEBUG ("Detach hook called");
1852 empathy_chat_window_move_chat (window, new_window, chat);
1854 gtk_widget_show (GTK_WIDGET (new_window));
1855 //gtk_window_move (GTK_WINDOW (new_window), x, y);
1861 chat_window_page_switched_cb (GtkNotebook *notebook,
1864 EmpathyChatWindow *self)
1866 EmpathyChat *chat = EMPATHY_CHAT (child);
1868 DEBUG ("Page switched");
1870 if (self->priv->page_added)
1872 self->priv->page_added = FALSE;
1873 empathy_chat_scroll_down (chat);
1875 else if (self->priv->current_chat == chat)
1880 self->priv->current_chat = chat;
1881 empathy_chat_messages_read (chat);
1883 chat_window_update_chat_tab (chat);
1887 chat_window_page_added_cb (GtkNotebook *notebook,
1890 EmpathyChatWindow *self)
1894 /* If we just received DND to the same window, we don't want
1895 * to do anything here like removing the tab and then readding
1896 * it, so we return here and in "page-added".
1898 if (self->priv->dnd_same_window)
1900 DEBUG ("Page added (back to the same window)");
1901 self->priv->dnd_same_window = FALSE;
1905 DEBUG ("Page added");
1907 /* Get chat object */
1908 chat = EMPATHY_CHAT (child);
1910 /* Connect chat signals for this window */
1911 g_signal_connect (chat, "composing",
1912 G_CALLBACK (chat_window_composing_cb), self);
1913 g_signal_connect (chat, "new-message",
1914 G_CALLBACK (chat_window_new_message_cb), self);
1915 g_signal_connect (chat, "part-command-entered",
1916 G_CALLBACK (chat_window_command_part), NULL);
1917 g_signal_connect (chat, "notify::tp-chat",
1918 G_CALLBACK (chat_window_update_chat_tab), self);
1920 /* Set flag so we know to perform some special operations on
1921 * switch page due to the new page being added.
1923 self->priv->page_added = TRUE;
1925 /* Get list of chats up to date */
1926 self->priv->chats = g_list_append (self->priv->chats, chat);
1928 chat_window_update_chat_tab (chat);
1932 chat_window_page_removed_cb (GtkNotebook *notebook,
1935 EmpathyChatWindow *self)
1939 /* If we just received DND to the same window, we don't want
1940 * to do anything here like removing the tab and then readding
1941 * it, so we return here and in "page-added".
1943 if (self->priv->dnd_same_window)
1945 DEBUG ("Page removed (and will be readded to same window)");
1949 DEBUG ("Page removed");
1951 /* Get chat object */
1952 chat = EMPATHY_CHAT (child);
1954 /* Disconnect all signal handlers for this chat and this window */
1955 g_signal_handlers_disconnect_by_func (chat,
1956 G_CALLBACK (chat_window_composing_cb), self);
1957 g_signal_handlers_disconnect_by_func (chat,
1958 G_CALLBACK (chat_window_new_message_cb), self);
1959 g_signal_handlers_disconnect_by_func (chat,
1960 G_CALLBACK (chat_window_update_chat_tab), self);
1962 /* Keep list of chats up to date */
1963 self->priv->chats = g_list_remove (self->priv->chats, chat);
1964 empathy_chat_messages_read (chat);
1966 if (self->priv->chats == NULL)
1968 gtk_widget_destroy (GTK_WIDGET (self));
1972 chat_window_update (self, TRUE);
1977 chat_window_focus_in_event_cb (GtkWidget *widget,
1979 EmpathyChatWindow *self)
1981 if (self->priv->current_chat == NULL) {
1984 empathy_chat_messages_read (self->priv->current_chat);
1986 chat_window_set_urgency_hint (self, FALSE);
1988 /* Update the title, since we now mark all unread messages as read. */
1989 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1995 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1996 EmpathyChatWindow *self)
1998 chat_window_contact_menu_update (self);
2002 chat_window_focus_out_event_cb (GtkWidget *widget,
2004 EmpathyChatWindow *self)
2006 if (self->priv->individual_mgr != NULL)
2009 /* Keep the individual manager alive so we won't fetch everything from Folks
2010 * each time we need to use it. Loading FolksAggregator can takes quite a
2011 * while (if user has a huge LDAP abook for example) and it blocks
2012 * the mainloop during most of this loading. We workaround this by loading
2013 * it when the chat window has been unfocused and so, hopefully, not impact
2014 * the reactivity of the chat window too much.
2016 * The individual manager (and so Folks) is needed to know to which
2017 * FolksIndividual a TpContact belongs, including:
2018 * - empathy_chat_get_contact_menu: to list all the personas of the contact
2019 * - empathy_display_individual_info: to invoke gnome-contacts with the
2020 * FolksIndividual.id of the contact
2021 * - drag_data_received_individual_id: to find the individual associated
2022 * with the ID we received from the DnD in order to invite him.
2024 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2026 if (!empathy_individual_manager_get_contacts_loaded (
2027 self->priv->individual_mgr))
2029 /* We want to update the contact menu when Folks is loaded so we can
2030 * list all the personas of the contact. */
2031 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2032 G_CALLBACK (contacts_loaded_cb), self, 0);
2035 g_object_notify (G_OBJECT (self), "individual-manager");
2041 chat_window_drag_drop (GtkWidget *widget,
2042 GdkDragContext *context,
2046 EmpathyChatWindow *self)
2050 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2051 if (target == GDK_NONE)
2052 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2054 if (target != GDK_NONE)
2056 gtk_drag_get_data (widget, context, target, time_);
2064 chat_window_drag_motion (GtkWidget *widget,
2065 GdkDragContext *context,
2069 EmpathyChatWindow *self)
2073 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2075 if (target != GDK_NONE)
2077 /* This is a file drag. Ensure the contact is online and set the
2078 drag type to COPY. Note that it's possible that the tab will
2079 be switched by GTK+ after a timeout from drag_motion without
2080 getting another drag_motion to disable the drop. You have
2081 to hold your mouse really still.
2083 EmpathyContact *contact;
2085 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2087 /* contact is NULL for multi-user chats. We don't do
2088 * file transfers to MUCs. We also don't send files
2089 * to offline contacts or contacts that don't support
2092 if ((contact == NULL) || !empathy_contact_is_online (contact))
2094 gdk_drag_status (context, 0, time_);
2098 if (!(empathy_contact_get_capabilities (contact)
2099 & EMPATHY_CAPABILITIES_FT))
2101 gdk_drag_status (context, 0, time_);
2105 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2109 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2110 if (target != GDK_NONE)
2112 /* This is a drag of a contact from a contact list. Set to COPY.
2113 FIXME: If this drag is to a MUC window, it invites the user.
2114 Otherwise, it opens a chat. Should we use a different drag
2115 type for invites? Should we allow ASK?
2117 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2125 drag_data_received_individual_id (EmpathyChatWindow *self,
2127 GdkDragContext *context,
2130 GtkSelectionData *selection,
2135 FolksIndividual *individual;
2136 EmpathyTpChat *chat;
2137 TpContact *tp_contact;
2139 EmpathyContact *contact;
2141 id = (const gchar *) gtk_selection_data_get_data (selection);
2143 DEBUG ("DND invididual %s", id);
2145 if (self->priv->current_chat == NULL)
2148 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2152 if (!empathy_tp_chat_can_add_contact (chat))
2154 DEBUG ("Can't invite contact to %s",
2155 tp_proxy_get_object_path (chat));
2159 if (self->priv->individual_mgr == NULL)
2160 /* Not likely as we have to focus out the chat window in order to start
2161 * the DnD but best to be safe. */
2164 individual = empathy_individual_manager_lookup_member (
2165 self->priv->individual_mgr, id);
2166 if (individual == NULL)
2168 DEBUG ("Failed to find individual %s", id);
2172 conn = tp_channel_get_connection ((TpChannel *) chat);
2173 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2174 if (tp_contact == NULL)
2176 DEBUG ("Can't find a TpContact on connection %s for %s",
2177 tp_proxy_get_object_path (conn), id);
2181 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2182 tp_channel_get_identifier ((TpChannel *) chat));
2184 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2185 empathy_tp_chat_add (chat, contact, NULL);
2186 g_object_unref (contact);
2189 gtk_drag_finish (context, TRUE, FALSE, time_);
2193 chat_window_drag_data_received (GtkWidget *widget,
2194 GdkDragContext *context,
2197 GtkSelectionData *selection,
2200 EmpathyChatWindow *self)
2202 if (info == DND_DRAG_TYPE_CONTACT_ID)
2204 EmpathyChat *chat = NULL;
2205 EmpathyChatWindow *old_window;
2206 TpAccount *account = NULL;
2207 EmpathyClientFactory *factory;
2210 const gchar *account_id;
2211 const gchar *contact_id;
2213 id = (const gchar*) gtk_selection_data_get_data (selection);
2215 factory = empathy_client_factory_dup ();
2217 DEBUG ("DND contact from roster with id:'%s'", id);
2219 strv = g_strsplit (id, ":", 2);
2220 if (g_strv_length (strv) == 2)
2222 account_id = strv[0];
2223 contact_id = strv[1];
2225 account = tp_simple_client_factory_ensure_account (
2226 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2228 g_object_unref (factory);
2229 if (account != NULL)
2230 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2233 if (account == NULL)
2236 gtk_drag_finish (context, FALSE, FALSE, time_);
2242 empathy_chat_with_contact_id (account, contact_id,
2243 empathy_get_current_action_time (), NULL, NULL);
2251 old_window = chat_window_find_chat (chat);
2254 if (old_window == self)
2256 gtk_drag_finish (context, TRUE, FALSE, time_);
2260 empathy_chat_window_move_chat (old_window, self, chat);
2264 empathy_chat_window_add_chat (self, chat);
2267 /* Added to take care of any outstanding chat events */
2268 empathy_chat_window_present_chat (chat,
2269 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2271 /* We should return TRUE to remove the data when doing
2272 * GDK_ACTION_MOVE, but we don't here otherwise it has
2273 * weird consequences, and we handle that internally
2274 * anyway with add_chat () and remove_chat ().
2276 gtk_drag_finish (context, TRUE, FALSE, time_);
2278 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2280 drag_data_received_individual_id (self, widget, context, x, y,
2281 selection, info, time_);
2283 else if (info == DND_DRAG_TYPE_URI_LIST)
2285 EmpathyContact *contact;
2288 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2290 /* contact is NULL when current_chat is a multi-user chat.
2291 * We don't do file transfers to MUCs, so just cancel the drag.
2293 if (contact == NULL)
2295 gtk_drag_finish (context, TRUE, FALSE, time_);
2299 data = (const gchar *) gtk_selection_data_get_data (selection);
2300 empathy_send_file_from_uri_list (contact, data);
2302 gtk_drag_finish (context, TRUE, FALSE, time_);
2304 else if (info == DND_DRAG_TYPE_TAB)
2307 EmpathyChatWindow *old_window = NULL;
2311 chat = (void *) gtk_selection_data_get_data (selection);
2312 old_window = chat_window_find_chat (*chat);
2316 self->priv->dnd_same_window = (old_window == self);
2318 DEBUG ("DND tab (within same window: %s)",
2319 self->priv->dnd_same_window ? "Yes" : "No");
2324 DEBUG ("DND from unknown source");
2325 gtk_drag_finish (context, FALSE, FALSE, time_);
2330 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2331 guint num_chats_in_manager,
2332 EmpathyChatWindow *self)
2334 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2335 num_chats_in_manager > 0);
2339 chat_window_finalize (GObject *object)
2341 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2343 DEBUG ("Finalized: %p", object);
2345 g_object_unref (self->priv->ui_manager);
2346 g_object_unref (self->priv->chatroom_manager);
2347 g_object_unref (self->priv->notify_mgr);
2348 g_object_unref (self->priv->gsettings_chat);
2349 g_object_unref (self->priv->gsettings_notif);
2350 g_object_unref (self->priv->gsettings_ui);
2351 g_object_unref (self->priv->sound_mgr);
2352 g_clear_object (&self->priv->individual_mgr);
2354 if (self->priv->notification != NULL)
2356 notify_notification_close (self->priv->notification, NULL);
2357 self->priv->notification = NULL;
2360 if (self->priv->contact_targets)
2361 gtk_target_list_unref (self->priv->contact_targets);
2363 if (self->priv->file_targets)
2364 gtk_target_list_unref (self->priv->file_targets);
2366 if (self->priv->chat_manager)
2368 g_signal_handler_disconnect (self->priv->chat_manager,
2369 self->priv->chat_manager_chats_changed_id);
2370 g_object_unref (self->priv->chat_manager);
2371 self->priv->chat_manager = NULL;
2374 chat_windows = g_list_remove (chat_windows, self);
2376 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2380 chat_window_get_property (GObject *object,
2385 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2387 switch (property_id)
2389 case PROP_INDIVIDUAL_MGR:
2390 g_value_set_object (value, self->priv->individual_mgr);
2392 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2398 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2400 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2403 object_class->get_property = chat_window_get_property;
2404 object_class->finalize = chat_window_finalize;
2406 spec = g_param_spec_object ("individual-manager", "individual-manager",
2407 "EmpathyIndividualManager",
2408 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2409 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2410 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2412 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2416 chat_window_chat_new_message_cb (GSimpleAction *action,
2417 GVariant *parameter,
2420 EmpathyChatWindow *self = user_data;
2422 //empathy_new_message_dialog_show (GTK_WINDOW (self));
2427 empathy_chat_window_init (EmpathyChatWindow *self)
2430 GtkAccelGroup *accel_group;
2435 GtkWidget *chat_vbox;
2436 GtkWidget *main_box;
2438 EmpathySmileyManager *smiley_manager;
2440 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2441 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2443 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2444 gui = tpaw_builder_get_file (filename,
2445 "chat_vbox", &chat_vbox,
2446 "main_box", &main_box,
2447 "ui_manager", &self->priv->ui_manager,
2448 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2449 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2450 "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2451 "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2452 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2453 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2454 "menu_edit_cut", &self->priv->menu_edit_cut,
2455 "menu_edit_copy", &self->priv->menu_edit_copy,
2456 "menu_edit_paste", &self->priv->menu_edit_paste,
2457 "menu_edit_find", &self->priv->menu_edit_find,
2458 "menu_tabs_next", &self->priv->menu_tabs_next,
2459 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2460 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2461 "menu_tabs_left", &self->priv->menu_tabs_left,
2462 "menu_tabs_right", &self->priv->menu_tabs_right,
2463 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2467 tpaw_builder_connect (gui, self,
2468 "menu_conv", "activate", chat_window_conv_activate_cb,
2469 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2470 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2471 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2472 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2473 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2474 "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2475 "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2476 "menu_conv_close", "activate", chat_window_close_activate_cb,
2477 "menu_edit", "activate", chat_window_edit_activate_cb,
2478 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2479 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2480 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2481 "menu_edit_find", "activate", chat_window_find_activate_cb,
2482 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2483 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2484 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2485 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2486 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2487 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2488 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2489 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2492 empathy_set_css_provider (GTK_WIDGET (self));
2494 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2495 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2496 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2497 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2499 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2501 self->priv->notebook = gtk_notebook_new ();
2502 //gtk_notebook_set_show_tabs (GTK_NOTEBOOK (self->priv->notebook), FALSE);
2504 g_signal_connect (self->priv->notebook, "create-window",
2505 G_CALLBACK (notebook_create_window_cb), self);
2507 gtk_container_add (GTK_CONTAINER (self), main_box);
2509 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2510 "EmpathyChatWindow");
2511 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2512 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2513 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2514 gtk_widget_show (self->priv->notebook);
2516 #if 0 /* no top level window yet at this point */
2518 accel_group = gtk_accel_group_new ();
2519 gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), accel_group);
2521 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2523 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2526 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2530 g_object_unref (accel_group);
2533 /* Set up drag target lists */
2534 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2535 G_N_ELEMENTS (drag_types_dest_contact));
2537 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2538 G_N_ELEMENTS (drag_types_dest_file));
2540 /* Set up smiley menu */
2541 smiley_manager = empathy_smiley_manager_dup_singleton ();
2542 submenu = empathy_smiley_menu_new (smiley_manager,
2543 chat_window_insert_smiley_activate_cb, self);
2545 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2546 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2547 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2548 g_object_unref (smiley_manager);
2550 /* Set up signals we can't do with ui file since we may need to
2551 * block/unblock them at some later stage.
2554 g_signal_connect (self, "delete_event",
2555 G_CALLBACK (chat_window_delete_event_cb), self);
2556 g_signal_connect (self, "focus_in_event",
2557 G_CALLBACK (chat_window_focus_in_event_cb), self);
2558 g_signal_connect (self, "focus_out_event",
2559 G_CALLBACK (chat_window_focus_out_event_cb), self);
2560 g_signal_connect_after (self->priv->notebook, "switch_page",
2561 G_CALLBACK (chat_window_page_switched_cb), self);
2562 g_signal_connect (self->priv->notebook, "page_added",
2563 G_CALLBACK (chat_window_page_added_cb), self);
2564 g_signal_connect (self->priv->notebook, "page_removed",
2565 G_CALLBACK (chat_window_page_removed_cb), self);
2567 /* Set up drag and drop */
2568 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2569 GTK_DEST_DEFAULT_HIGHLIGHT,
2571 G_N_ELEMENTS (drag_types_dest),
2572 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2574 /* connect_after to allow GtkNotebook's built-in tab switching */
2575 g_signal_connect_after (self->priv->notebook, "drag-motion",
2576 G_CALLBACK (chat_window_drag_motion), self);
2577 g_signal_connect (self->priv->notebook, "drag-data-received",
2578 G_CALLBACK (chat_window_drag_data_received), self);
2579 g_signal_connect (self->priv->notebook, "drag-drop",
2580 G_CALLBACK (chat_window_drag_drop), self);
2582 chat_windows = g_list_prepend (chat_windows, self);
2584 /* Set up private details */
2585 self->priv->chats = NULL;
2586 self->priv->current_chat = NULL;
2587 self->priv->notification = NULL;
2589 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2591 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2592 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2593 self->priv->chat_manager, "closed-chats-changed",
2594 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2596 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2597 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2599 g_object_ref (self->priv->ui_manager);
2600 g_object_unref (gui);
2603 /* Returns the window to open a new tab in if there is a suitable window,
2604 * otherwise, returns NULL indicating that a new window should be added.
2606 static EmpathyChatWindow *
2607 empathy_chat_window_get_default (gboolean room)
2609 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2611 gboolean separate_windows = TRUE;
2613 separate_windows = g_settings_get_boolean (gsettings,
2614 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2615 separate_windows = FALSE;
2617 g_object_unref (gsettings);
2619 if (separate_windows)
2620 /* Always create a new window */
2623 for (l = chat_windows; l; l = l->next)
2625 EmpathyChatWindow *chat_window;
2626 guint nb_rooms, nb_private;
2628 chat_window = l->data;
2631 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2633 /* Skip the window if there aren't any rooms in it */
2634 if (room && nb_rooms == 0)
2637 /* Skip the window if there aren't any 1-1 chats in it */
2638 if (!room && nb_private == 0)
2649 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2653 GtkWidget *popup_label;
2655 GValue value = { 0, };
2657 g_return_if_fail (self != NULL);
2658 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2660 /* Reference the chat object */
2661 g_object_ref (chat);
2663 /* If this window has just been created, position it */
2664 if (self->priv->chats == NULL)
2666 const gchar *name = "chat-window";
2667 gboolean separate_windows;
2669 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2670 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2671 separate_windows = FALSE;
2673 if (empathy_chat_is_room (chat))
2674 name = "room-window";
2676 if (separate_windows)
2680 /* Save current position of the window */
2681 //gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2683 /* First bind to the 'generic' name. So new window for which we didn't
2684 * save a geometry yet will have the geometry of the last saved
2685 * window (bgo #601191). */
2686 //empathy_geometry_bind (GTK_WINDOW (self), name);
2688 /* Restore previous position of the window so the newly created window
2689 * won't be in the same position as the latest saved window and so
2690 * completely hide it. */
2691 //gtk_window_move (GTK_WINDOW (self), x, y);
2693 /* Then bind it to the name of the contact/room so we'll save the
2694 * geometry specific to this window */
2695 name = empathy_chat_get_id (chat);
2698 //empathy_geometry_bind (GTK_WINDOW (self), name);
2701 child = GTK_WIDGET (chat);
2702 label = chat_window_create_label (self, chat, TRUE);
2703 popup_label = chat_window_create_label (self, chat, FALSE);
2704 gtk_widget_show (child);
2706 g_signal_connect (chat, "notify::name",
2707 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2708 g_signal_connect (chat, "notify::subject",
2709 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2710 g_signal_connect (chat, "notify::remote-contact",
2711 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2712 g_signal_connect (chat, "notify::sms-channel",
2713 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2714 g_signal_connect (chat, "notify::n-messages-sending",
2715 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2716 g_signal_connect (chat, "notify::nb-unread-messages",
2717 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2718 chat_window_chat_notify_cb (chat);
2720 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2722 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2723 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2724 g_value_init (&value, G_TYPE_BOOLEAN);
2725 g_value_set_boolean (&value, TRUE);
2726 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2727 child, "tab-expand" , &value);
2728 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2729 child, "tab-fill" , &value);
2730 g_value_unset (&value);
2732 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2736 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2740 EmpathyContact *remote_contact;
2741 EmpathyChatManager *chat_manager;
2743 g_return_if_fail (self != NULL);
2744 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2746 g_signal_handlers_disconnect_by_func (chat,
2747 chat_window_chat_notify_cb, NULL);
2749 remote_contact = g_object_get_data (G_OBJECT (chat),
2750 "chat-window-remote-contact");
2754 g_signal_handlers_disconnect_by_func (remote_contact,
2755 chat_window_update_chat_tab, chat);
2758 chat_manager = empathy_chat_manager_dup_singleton ();
2759 empathy_chat_manager_closed_chat (chat_manager, chat);
2760 g_object_unref (chat_manager);
2762 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2764 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2766 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2768 g_object_unref (chat);
2772 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2773 EmpathyChatWindow *new_window,
2778 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2779 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2780 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2782 widget = GTK_WIDGET (chat);
2784 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2785 G_OBJECT (widget)->ref_count);
2787 /* We reference here to make sure we don't loose the widget
2788 * and the EmpathyChat object during the move.
2790 g_object_ref (chat);
2791 g_object_ref (widget);
2793 empathy_chat_window_remove_chat (old_window, chat);
2794 empathy_chat_window_add_chat (new_window, chat);
2796 g_object_unref (widget);
2797 g_object_unref (chat);
2801 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2806 g_return_if_fail (self != NULL);
2807 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2809 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2812 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2817 empathy_chat_window_find_chat (TpAccount *account,
2819 gboolean sms_channel)
2823 g_return_val_if_fail (!TPAW_STR_EMPTY (id), NULL);
2825 for (l = chat_windows; l; l = l->next)
2827 EmpathyChatWindow *window = l->data;
2830 for (ll = window->priv->chats; ll; ll = ll->next)
2836 if (account == empathy_chat_get_account (chat) &&
2837 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2838 sms_channel == empathy_chat_is_sms_channel (chat))
2847 empathy_chat_window_present_chat (EmpathyChat *chat,
2850 EmpathyChatWindow *self;
2851 guint32 x_timestamp;
2854 /* initial window */
2855 self = empathy_chat_window_new ();
2856 gtk_widget_show (GTK_WIDGET (self));
2860 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2862 self = chat_window_find_chat (chat);
2864 /* If the chat has no window, create one */
2867 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2870 self = empathy_chat_window_new ();
2872 /* we want to display the newly created window even if we
2873 * don't present it */
2874 gtk_widget_show (GTK_WIDGET (self));
2877 empathy_chat_window_add_chat (self, chat);
2880 /* Don't force the window to show itself when it wasn't
2881 * an action by the user
2883 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2886 if (x_timestamp != GDK_CURRENT_TIME)
2888 /* Don't present or switch tab if the action was earlier than the
2889 * last actions X time, accounting for overflow and the first ever
2892 if (self->priv->x_user_action_time != 0
2893 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2896 self->priv->x_user_action_time = x_timestamp;
2899 empathy_chat_window_switch_to_chat (self, chat);
2901 /* Don't use tpaw_window_present_with_time () which would move the window
2902 * to our current desktop but move to the window's desktop instead. This is
2903 * more coherent with Shell's 'app is ready' notication which moves the view
2904 * to the app desktop rather than moving the app itself. */
2905 empathy_move_to_window_desktop (GTK_WINDOW (gtk_widget_get_toplevel(GTK_WIDGET(self))), x_timestamp);
2907 gtk_widget_grab_focus (chat->input_text_view);
2912 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2917 guint _nb_rooms = 0, _nb_private = 0;
2919 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2921 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2927 if (nb_rooms != NULL)
2928 *nb_rooms = _nb_rooms;
2929 if (nb_private != NULL)
2930 *nb_private = _nb_private;
2933 EmpathyIndividualManager *
2934 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2936 return self->priv->individual_mgr;