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-2010 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-client-factory.h>
42 #include <libempathy/empathy-contact.h>
43 #include <libempathy/empathy-message.h>
44 #include <libempathy/empathy-chatroom-manager.h>
45 #include <libempathy/empathy-gsettings.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-request-util.h>
48 #include <libempathy/empathy-individual-manager.h>
50 #include <libempathy-gtk/empathy-images.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-manager.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 gboolean dnd_same_window;
79 EmpathyChatroomManager *chatroom_manager;
80 EmpathyNotifyManager *notify_mgr;
83 NotifyNotification *notification;
85 GtkTargetList *contact_targets;
86 GtkTargetList *file_targets;
88 EmpathyChatManager *chat_manager;
89 gulong chat_manager_chats_changed_id;
92 GtkUIManager *ui_manager;
93 GtkAction *menu_conv_insert_smiley;
94 GtkAction *menu_conv_favorite;
95 GtkAction *menu_conv_always_urgent;
96 GtkAction *menu_conv_toggle_contacts;
98 GtkAction *menu_edit_cut;
99 GtkAction *menu_edit_copy;
100 GtkAction *menu_edit_paste;
101 GtkAction *menu_edit_find;
103 GtkAction *menu_tabs_next;
104 GtkAction *menu_tabs_prev;
105 GtkAction *menu_tabs_undo_close_tab;
106 GtkAction *menu_tabs_left;
107 GtkAction *menu_tabs_right;
108 GtkAction *menu_tabs_detach;
110 /* Last user action time we acted upon to show a tab */
111 guint32 x_user_action_time;
113 GSettings *gsettings_chat;
114 GSettings *gsettings_notif;
115 GSettings *gsettings_ui;
117 EmpathySoundManager *sound_mgr;
118 } EmpathyChatWindowPriv;
120 static GList *chat_windows = NULL;
122 static const guint tab_accel_keys[] = {
123 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
124 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
128 DND_DRAG_TYPE_CONTACT_ID,
129 DND_DRAG_TYPE_INDIVIDUAL_ID,
130 DND_DRAG_TYPE_URI_LIST,
134 static const GtkTargetEntry drag_types_dest[] = {
135 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
136 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
137 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
138 /* FIXME: disabled because of bug #640513
139 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
140 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
144 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[] = {
150 /* must be first to be prioritized, in order to receive the
151 * note's file path from Tomboy instead of an URI */
152 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
153 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
156 static void chat_window_update (EmpathyChatWindow *window,
157 gboolean update_contact_menu);
159 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
162 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
165 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
166 EmpathyChatWindow *new_window,
169 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
173 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
176 chat_window_accel_cb (GtkAccelGroup *accelgroup,
180 EmpathyChatWindow *window)
182 EmpathyChatWindowPriv *priv;
186 priv = GET_PRIV (window);
188 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
189 if (tab_accel_keys[i] == key) {
196 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
200 static EmpathyChatWindow *
201 chat_window_find_chat (EmpathyChat *chat)
203 EmpathyChatWindowPriv *priv;
206 for (l = chat_windows; l; l = l->next) {
207 priv = GET_PRIV (l->data);
208 ll = g_list_find (priv->chats, chat);
218 remove_all_chats (EmpathyChatWindow *window)
220 EmpathyChatWindowPriv *priv;
222 priv = GET_PRIV (window);
223 g_object_ref (window);
225 while (priv->chats) {
226 empathy_chat_window_remove_chat (window, priv->chats->data);
229 g_object_unref (window);
233 confirm_close_response_cb (GtkWidget *dialog,
235 EmpathyChatWindow *window)
239 chat = g_object_get_data (G_OBJECT (dialog), "chat");
241 gtk_widget_destroy (dialog);
243 if (response != GTK_RESPONSE_ACCEPT)
247 empathy_chat_window_remove_chat (window, chat);
249 remove_all_chats (window);
254 confirm_close (EmpathyChatWindow *window,
255 gboolean close_window,
259 EmpathyChatWindowPriv *priv;
261 gchar *primary, *secondary;
263 g_return_if_fail (n_rooms > 0);
266 g_return_if_fail (chat == NULL);
268 g_return_if_fail (chat != NULL);
271 priv = GET_PRIV (window);
273 /* If there are no chats in this window, how could we possibly have got
276 g_return_if_fail (priv->chats != NULL);
278 /* Treat closing a window which only has one tab exactly like closing
281 if (close_window && priv->chats->next == NULL) {
282 close_window = FALSE;
283 chat = priv->chats->data;
287 primary = g_strdup (_("Close this window?"));
290 gchar *chat_name = empathy_chat_dup_name (chat);
291 secondary = g_strdup_printf (
292 _("Closing this window will leave %s. You will "
293 "not receive any further messages until you "
298 secondary = g_strdup_printf (
299 /* Note to translators: the number of chats will
300 * always be at least 2.
303 "Closing this window will leave a chat room. You will "
304 "not receive any further messages until you rejoin it.",
305 "Closing this window will leave %u chat rooms. You will "
306 "not receive any further messages until you rejoin them.",
311 gchar *chat_name = empathy_chat_dup_name (chat);
312 primary = g_strdup_printf (_("Leave %s?"), chat_name);
313 secondary = g_strdup (_("You will not receive any further messages from this chat "
314 "room until you rejoin it."));
318 dialog = gtk_message_dialog_new (
319 GTK_WINDOW (priv->dialog),
320 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
325 gtk_window_set_title (GTK_WINDOW (dialog), "");
326 g_object_set (dialog, "secondary-text", secondary, NULL);
331 gtk_dialog_add_button (GTK_DIALOG (dialog),
332 close_window ? _("Close window") : _("Leave room"),
333 GTK_RESPONSE_ACCEPT);
334 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
335 GTK_RESPONSE_ACCEPT);
338 g_object_set_data (G_OBJECT (dialog), "chat", chat);
341 g_signal_connect (dialog, "response",
342 G_CALLBACK (confirm_close_response_cb), window);
344 gtk_window_present (GTK_WINDOW (dialog));
347 /* Returns TRUE if we should check if the user really wants to leave. If it's
348 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
349 * the user is actually in the room as opposed to having been kicked or gone
350 * offline or something), then we should check.
353 chat_needs_close_confirmation (EmpathyChat *chat)
355 return (empathy_chat_is_room (chat)
356 && empathy_chat_get_tp_chat (chat) != NULL);
360 maybe_close_chat (EmpathyChatWindow *window,
363 g_return_if_fail (chat != NULL);
365 if (chat_needs_close_confirmation (chat)) {
366 confirm_close (window, FALSE, 1, chat);
368 empathy_chat_window_remove_chat (window, chat);
373 chat_window_close_clicked_cb (GtkAction *action,
376 EmpathyChatWindow *window;
378 window = chat_window_find_chat (chat);
379 maybe_close_chat (window, chat);
383 chat_tab_style_updated_cb (GtkWidget *hbox,
387 int char_width, h, w;
388 PangoContext *context;
389 const PangoFontDescription *font_desc;
390 PangoFontMetrics *metrics;
392 button = g_object_get_data (G_OBJECT (user_data),
393 "chat-window-tab-close-button");
394 context = gtk_widget_get_pango_context (hbox);
396 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
397 GTK_STATE_FLAG_NORMAL);
399 metrics = pango_context_get_metrics (context, font_desc,
400 pango_context_get_language (context));
401 char_width = pango_font_metrics_get_approximate_char_width (metrics);
402 pango_font_metrics_unref (metrics);
404 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
405 GTK_ICON_SIZE_MENU, &w, &h);
407 /* Request at least about 12 chars width plus at least space for the status
408 * image and the close button */
409 gtk_widget_set_size_request (hbox,
410 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
412 gtk_widget_set_size_request (button, w, h);
416 create_close_button (void)
418 GtkWidget *button, *image;
419 GtkStyleContext *context;
421 button = gtk_button_new ();
423 context = gtk_widget_get_style_context (button);
424 gtk_style_context_add_class (context, "empathy-tab-close-button");
426 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
427 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
429 /* We don't want focus/keynav for the button to avoid clutter, and
430 * Ctrl-W works anyway.
432 gtk_widget_set_can_focus (button, FALSE);
433 gtk_widget_set_can_default (button, FALSE);
435 image = gtk_image_new_from_icon_name ("window-close-symbolic",
437 gtk_widget_show (image);
439 gtk_container_add (GTK_CONTAINER (button), image);
445 chat_window_create_label (EmpathyChatWindow *window,
447 gboolean is_tab_label)
450 GtkWidget *name_label;
451 GtkWidget *status_image;
452 GtkWidget *event_box;
453 GtkWidget *event_box_hbox;
454 PangoAttrList *attr_list;
455 PangoAttribute *attr;
457 /* The spacing between the button and the label. */
458 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
460 event_box = gtk_event_box_new ();
461 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
463 name_label = gtk_label_new (NULL);
465 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
467 attr_list = pango_attr_list_new ();
468 attr = pango_attr_scale_new (1/1.2);
469 attr->start_index = 0;
470 attr->end_index = -1;
471 pango_attr_list_insert (attr_list, attr);
472 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
473 pango_attr_list_unref (attr_list);
475 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
476 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
477 g_object_set_data (G_OBJECT (chat),
478 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
481 status_image = gtk_image_new ();
483 /* Spacing between the icon and label. */
484 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
486 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
487 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
489 g_object_set_data (G_OBJECT (chat),
490 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
492 g_object_set_data (G_OBJECT (chat),
493 is_tab_label ? "chat-window-tab-tooltip-widget" : "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);
500 GtkWidget *close_button;
501 GtkWidget *sending_spinner;
503 sending_spinner = gtk_spinner_new ();
505 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
507 g_object_set_data (G_OBJECT (chat),
508 "chat-window-tab-sending-spinner",
511 close_button = create_close_button ();
512 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
514 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
516 g_signal_connect (close_button,
518 G_CALLBACK (chat_window_close_clicked_cb),
521 /* React to theme changes and also setup the size correctly. */
522 g_signal_connect (hbox,
524 G_CALLBACK (chat_tab_style_updated_cb),
528 gtk_widget_show_all (hbox);
534 _submenu_notify_visible_changed_cb (GObject *object,
538 g_signal_handlers_disconnect_by_func (object,
539 _submenu_notify_visible_changed_cb,
541 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
545 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
550 gboolean wrap_around;
551 gboolean is_connected;
554 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (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 (priv->current_chat) != NULL;
561 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
563 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
565 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
566 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
567 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
568 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
572 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
573 EmpathyChatWindow *self)
575 EmpathyTpChat *tp_chat;
576 TpConnection *connection;
578 gboolean sensitive = FALSE;
580 g_return_if_fail (priv->current_chat != NULL);
582 action = gtk_ui_manager_get_action (priv->ui_manager,
583 "/chats_menubar/menu_conv/menu_conv_invite_participant");
584 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
586 if (tp_chat != NULL) {
587 connection = tp_channel_borrow_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 (EmpathyChatWindowPriv *priv,
599 EmpathyChatWindow *window)
601 GtkWidget *menu, *submenu, *orig_submenu;
603 menu = gtk_ui_manager_get_widget (priv->ui_manager,
604 "/chats_menubar/menu_contact");
605 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
607 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
608 submenu = empathy_chat_get_contact_menu (priv->current_chat);
610 if (submenu != NULL) {
611 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
612 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
614 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
615 gtk_widget_show (menu);
616 gtk_widget_set_sensitive (menu, TRUE);
618 gtk_widget_set_sensitive (menu, FALSE);
621 tp_g_signal_connect_object (orig_submenu,
623 (GCallback)_submenu_notify_visible_changed_cb,
629 get_all_unread_messages (EmpathyChatWindowPriv *priv)
634 for (l = priv->chats; l != NULL; l = g_list_next (l))
635 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
641 get_window_title_name (EmpathyChatWindowPriv *priv)
643 gchar *active_name, *ret;
645 guint current_unread_msgs;
647 nb_chats = g_list_length (priv->chats);
648 g_assert (nb_chats > 0);
650 active_name = empathy_chat_dup_name (priv->current_chat);
652 current_unread_msgs = empathy_chat_get_nb_unread_messages (
657 if (current_unread_msgs == 0)
658 ret = g_strdup (active_name);
660 ret = g_strdup_printf (ngettext (
662 "%s (%d unread)", current_unread_msgs),
663 active_name, current_unread_msgs);
665 guint nb_others = nb_chats - 1;
666 guint all_unread_msgs;
668 all_unread_msgs = get_all_unread_messages (priv);
670 if (all_unread_msgs == 0) {
671 /* no unread message */
672 ret = g_strdup_printf (ngettext (
674 "%s (and %u others)", nb_others),
675 active_name, nb_others);
678 else if (all_unread_msgs == current_unread_msgs) {
679 /* unread messages are in the current tab */
680 ret = g_strdup_printf (ngettext (
682 "%s (%d unread)", current_unread_msgs),
683 active_name, current_unread_msgs);
686 else if (current_unread_msgs == 0) {
687 /* unread messages are in other tabs */
688 ret = g_strdup_printf (ngettext (
689 "%s (%d unread from others)",
690 "%s (%d unread from others)",
692 active_name, all_unread_msgs);
696 /* unread messages are in all the tabs */
697 ret = g_strdup_printf (ngettext (
698 "%s (%d unread from all)",
699 "%s (%d unread from all)",
701 active_name, all_unread_msgs);
705 g_free (active_name);
711 chat_window_title_update (EmpathyChatWindowPriv *priv)
715 name = get_window_title_name (priv);
716 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
721 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
724 EmpathyContact *remote_contact;
725 gboolean avatar_in_icon;
728 n_chats = g_list_length (priv->chats);
730 /* Update window icon */
732 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
733 EMPATHY_IMAGE_MESSAGE);
735 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
736 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
738 if (n_chats == 1 && avatar_in_icon) {
739 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
740 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
741 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
744 g_object_unref (icon);
747 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
753 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
757 GtkWidget *chat_close_button;
760 if (num_pages == 1) {
761 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
762 chat_close_button = g_object_get_data (G_OBJECT (chat),
763 "chat-window-tab-close-button");
764 gtk_widget_hide (chat_close_button);
766 for (i=0; i<num_pages; i++) {
767 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
768 chat_close_button = g_object_get_data (G_OBJECT (chat),
769 "chat-window-tab-close-button");
770 gtk_widget_show (chat_close_button);
776 chat_window_update (EmpathyChatWindow *window,
777 gboolean update_contact_menu)
779 EmpathyChatWindowPriv *priv = GET_PRIV (window);
782 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
784 /* Update Tab menu */
785 chat_window_menu_context_update (priv,
788 chat_window_conversation_menu_update (priv, window);
790 /* If this update is due to a focus-in event, we know the menu will be
791 the same as when we last left it, so no work to do. Besides, if we
792 swap out the menu on a focus-in, we may confuse any external global
794 if (update_contact_menu) {
795 chat_window_contact_menu_update (priv,
799 chat_window_title_update (priv);
801 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
803 chat_window_close_button_update (priv,
808 append_markup_printf (GString *string,
815 va_start (args, format);
817 tmp = g_markup_vprintf_escaped (format, args);
818 g_string_append (string, tmp);
825 chat_window_update_chat_tab_full (EmpathyChat *chat,
826 gboolean update_contact_menu)
828 EmpathyChatWindow *window;
829 EmpathyChatWindowPriv *priv;
830 EmpathyContact *remote_contact;
834 const gchar *subject;
835 const gchar *status = NULL;
839 const gchar *icon_name;
840 GtkWidget *tab_image;
841 GtkWidget *menu_image;
842 GtkWidget *sending_spinner;
845 window = chat_window_find_chat (chat);
849 priv = GET_PRIV (window);
851 /* Get information */
852 name = empathy_chat_dup_name (chat);
853 account = empathy_chat_get_account (chat);
854 subject = empathy_chat_get_subject (chat);
855 remote_contact = empathy_chat_get_remote_contact (chat);
857 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
858 name, tp_proxy_get_object_path (account), subject, remote_contact);
860 /* Update tab image */
861 if (empathy_chat_get_tp_chat (chat) == NULL) {
862 /* No TpChat, we are disconnected */
865 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
866 icon_name = EMPATHY_IMAGE_MESSAGE;
868 else if (remote_contact && empathy_chat_is_composing (chat)) {
869 icon_name = EMPATHY_IMAGE_TYPING;
871 else if (empathy_chat_is_sms_channel (chat)) {
872 icon_name = EMPATHY_IMAGE_SMS;
874 else if (remote_contact) {
875 icon_name = empathy_icon_name_for_contact (remote_contact);
877 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
880 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
881 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
882 if (icon_name != NULL) {
883 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
884 gtk_widget_show (tab_image);
885 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
886 gtk_widget_show (menu_image);
888 gtk_widget_hide (tab_image);
889 gtk_widget_hide (menu_image);
892 /* Update the sending spinner */
893 nb_sending = empathy_chat_get_n_messages_sending (chat);
894 sending_spinner = g_object_get_data (G_OBJECT (chat),
895 "chat-window-tab-sending-spinner");
897 g_object_set (sending_spinner,
898 "active", nb_sending > 0,
899 "visible", nb_sending > 0,
902 /* Update tab tooltip */
903 tooltip = g_string_new (NULL);
905 if (remote_contact) {
906 id = empathy_contact_get_id (remote_contact);
907 status = empathy_contact_get_presence_message (remote_contact);
912 if (empathy_chat_is_sms_channel (chat)) {
913 append_markup_printf (tooltip, "%s ", _("SMS:"));
916 append_markup_printf (tooltip,
917 "<b>%s</b><small> (%s)</small>",
919 tp_account_get_display_name (account));
921 if (nb_sending > 0) {
922 char *tmp = g_strdup_printf (
923 ngettext ("Sending %d message",
924 "Sending %d messages",
928 g_string_append (tooltip, "\n");
929 g_string_append (tooltip, tmp);
931 gtk_widget_set_tooltip_text (sending_spinner, tmp);
935 if (!EMP_STR_EMPTY (status)) {
936 append_markup_printf (tooltip, "\n<i>%s</i>", status);
940 append_markup_printf (tooltip, "\n<b>%s</b> %s",
941 _("Topic:"), subject);
944 if (remote_contact && empathy_chat_is_composing (chat)) {
945 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
948 if (remote_contact != NULL) {
949 const gchar * const *types;
951 types = empathy_contact_get_client_types (remote_contact);
952 if (types != NULL && !tp_strdiff (types[0], "phone")) {
953 /* I'm on a phone ! */
956 name = g_strdup_printf ("☎ %s", name);
961 markup = g_string_free (tooltip, FALSE);
962 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
963 gtk_widget_set_tooltip_markup (widget, markup);
964 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
965 gtk_widget_set_tooltip_markup (widget, markup);
968 /* Update tab and menu label */
969 if (empathy_chat_is_highlighted (chat)) {
970 markup = g_markup_printf_escaped (
971 "<span color=\"red\" weight=\"bold\">%s</span>",
974 markup = g_markup_escape_text (name, -1);
977 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
978 gtk_label_set_markup (GTK_LABEL (widget), markup);
979 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
980 gtk_label_set_markup (GTK_LABEL (widget), markup);
983 /* Update the window if it's the current chat */
984 if (priv->current_chat == chat) {
985 chat_window_update (window, update_contact_menu);
992 chat_window_update_chat_tab (EmpathyChat *chat)
994 chat_window_update_chat_tab_full (chat, TRUE);
998 chat_window_chat_notify_cb (EmpathyChat *chat)
1000 EmpathyChatWindow *window;
1001 EmpathyContact *old_remote_contact;
1002 EmpathyContact *remote_contact = NULL;
1004 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
1005 remote_contact = empathy_chat_get_remote_contact (chat);
1007 if (old_remote_contact != remote_contact) {
1008 /* The remote-contact associated with the chat changed, we need
1009 * to keep track of any change of that contact and update the
1010 * window each time. */
1011 if (remote_contact) {
1012 g_signal_connect_swapped (remote_contact, "notify",
1013 G_CALLBACK (chat_window_update_chat_tab),
1016 if (old_remote_contact) {
1017 g_signal_handlers_disconnect_by_func (old_remote_contact,
1018 chat_window_update_chat_tab,
1022 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1023 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1026 chat_window_update_chat_tab (chat);
1028 window = chat_window_find_chat (chat);
1029 if (window != NULL) {
1030 chat_window_update (window, FALSE);
1035 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1036 EmpathySmiley *smiley,
1039 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1041 GtkTextBuffer *buffer;
1044 chat = priv->current_chat;
1046 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1047 gtk_text_buffer_get_end_iter (buffer, &iter);
1048 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1052 chat_window_conv_activate_cb (GtkAction *action,
1053 EmpathyChatWindow *window)
1055 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1058 EmpathyContact *remote_contact = NULL;
1060 /* Favorite room menu */
1061 is_room = empathy_chat_is_room (priv->current_chat);
1065 gboolean found = FALSE;
1066 EmpathyChatroom *chatroom;
1068 room = empathy_chat_get_id (priv->current_chat);
1069 account = empathy_chat_get_account (priv->current_chat);
1070 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1072 if (chatroom != NULL)
1073 found = empathy_chatroom_is_favorite (chatroom);
1075 DEBUG ("This room %s favorite", found ? "is" : "is not");
1076 gtk_toggle_action_set_active (
1077 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1079 if (chatroom != NULL)
1080 found = empathy_chatroom_is_always_urgent (chatroom);
1082 gtk_toggle_action_set_active (
1083 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1086 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1087 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1089 /* Show contacts menu */
1090 g_object_get (priv->current_chat,
1091 "remote-contact", &remote_contact,
1092 "show-contacts", &active,
1094 if (remote_contact == NULL) {
1095 gtk_toggle_action_set_active (
1096 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1099 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1100 (remote_contact == NULL));
1101 if (remote_contact != NULL) {
1102 g_object_unref (remote_contact);
1107 chat_window_clear_activate_cb (GtkAction *action,
1108 EmpathyChatWindow *window)
1110 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1112 empathy_chat_clear (priv->current_chat);
1116 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1117 EmpathyChatWindow *window)
1119 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1124 EmpathyChatroom *chatroom;
1126 active = gtk_toggle_action_get_active (toggle_action);
1127 account = empathy_chat_get_account (priv->current_chat);
1128 room = empathy_chat_get_id (priv->current_chat);
1129 name = empathy_chat_dup_name (priv->current_chat);
1131 chatroom = empathy_chatroom_manager_ensure_chatroom (
1132 priv->chatroom_manager,
1137 empathy_chatroom_set_favorite (chatroom, active);
1138 g_object_unref (chatroom);
1143 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1144 EmpathyChatWindow *window)
1146 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1151 EmpathyChatroom *chatroom;
1153 active = gtk_toggle_action_get_active (toggle_action);
1154 account = empathy_chat_get_account (priv->current_chat);
1155 room = empathy_chat_get_id (priv->current_chat);
1156 name = empathy_chat_dup_name (priv->current_chat);
1158 chatroom = empathy_chatroom_manager_ensure_chatroom (
1159 priv->chatroom_manager,
1164 empathy_chatroom_set_always_urgent (chatroom, active);
1165 g_object_unref (chatroom);
1170 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1171 EmpathyChatWindow *window)
1173 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1176 active = gtk_toggle_action_get_active (toggle_action);
1178 empathy_chat_set_show_contacts (priv->current_chat, active);
1182 chat_window_invite_participant_activate_cb (GtkAction *action,
1183 EmpathyChatWindow *window)
1185 EmpathyChatWindowPriv *priv;
1187 EmpathyTpChat *tp_chat;
1190 priv = GET_PRIV (window);
1192 g_return_if_fail (priv->current_chat != NULL);
1194 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1196 dialog = empathy_invite_participant_dialog_new (
1197 GTK_WINDOW (priv->dialog), tp_chat);
1198 gtk_widget_show (dialog);
1200 response = gtk_dialog_run (GTK_DIALOG (dialog));
1202 if (response == GTK_RESPONSE_ACCEPT) {
1203 TpContact *tp_contact;
1204 EmpathyContact *contact;
1206 tp_contact = empathy_invite_participant_dialog_get_selected (
1207 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1208 if (tp_contact == NULL) goto out;
1210 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1212 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1214 g_object_unref (contact);
1218 gtk_widget_destroy (dialog);
1222 chat_window_close_activate_cb (GtkAction *action,
1223 EmpathyChatWindow *window)
1225 EmpathyChatWindowPriv *priv;
1227 priv = GET_PRIV (window);
1229 g_return_if_fail (priv->current_chat != NULL);
1231 maybe_close_chat (window, priv->current_chat);
1235 chat_window_edit_activate_cb (GtkAction *action,
1236 EmpathyChatWindow *window)
1238 EmpathyChatWindowPriv *priv;
1239 GtkClipboard *clipboard;
1240 GtkTextBuffer *buffer;
1241 gboolean text_available;
1243 priv = GET_PRIV (window);
1245 g_return_if_fail (priv->current_chat != NULL);
1247 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1248 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1249 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1250 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1254 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1255 if (gtk_text_buffer_get_has_selection (buffer)) {
1256 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1257 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1261 selection = empathy_theme_adium_get_has_selection (priv->current_chat->view);
1263 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1264 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1267 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1268 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1269 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1273 chat_window_cut_activate_cb (GtkAction *action,
1274 EmpathyChatWindow *window)
1276 EmpathyChatWindowPriv *priv;
1278 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1280 priv = GET_PRIV (window);
1282 empathy_chat_cut (priv->current_chat);
1286 chat_window_copy_activate_cb (GtkAction *action,
1287 EmpathyChatWindow *window)
1289 EmpathyChatWindowPriv *priv;
1291 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1293 priv = GET_PRIV (window);
1295 empathy_chat_copy (priv->current_chat);
1299 chat_window_paste_activate_cb (GtkAction *action,
1300 EmpathyChatWindow *window)
1302 EmpathyChatWindowPriv *priv;
1304 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1306 priv = GET_PRIV (window);
1308 empathy_chat_paste (priv->current_chat);
1312 chat_window_find_activate_cb (GtkAction *action,
1313 EmpathyChatWindow *window)
1315 EmpathyChatWindowPriv *priv;
1317 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1319 priv = GET_PRIV (window);
1321 empathy_chat_find (priv->current_chat);
1325 chat_window_tabs_next_activate_cb (GtkAction *action,
1326 EmpathyChatWindow *window)
1328 EmpathyChatWindowPriv *priv;
1329 gint index_, numPages;
1330 gboolean wrap_around;
1332 priv = GET_PRIV (window);
1334 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1335 &wrap_around, NULL);
1337 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1338 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1340 if (index_ == (numPages - 1) && wrap_around) {
1341 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1345 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1349 chat_window_tabs_previous_activate_cb (GtkAction *action,
1350 EmpathyChatWindow *window)
1352 EmpathyChatWindowPriv *priv;
1353 gint index_, numPages;
1354 gboolean wrap_around;
1356 priv = GET_PRIV (window);
1358 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1359 &wrap_around, NULL);
1361 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1362 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1364 if (index_ <= 0 && wrap_around) {
1365 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1369 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1373 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1374 EmpathyChatWindow *window)
1376 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1377 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1378 empathy_get_current_action_time ());
1382 chat_window_tabs_left_activate_cb (GtkAction *action,
1383 EmpathyChatWindow *window)
1385 EmpathyChatWindowPriv *priv;
1387 gint index_, num_pages;
1389 priv = GET_PRIV (window);
1391 chat = priv->current_chat;
1392 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1397 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1401 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1402 chat_window_menu_context_update (priv, num_pages);
1406 chat_window_tabs_right_activate_cb (GtkAction *action,
1407 EmpathyChatWindow *window)
1409 EmpathyChatWindowPriv *priv;
1411 gint index_, num_pages;
1413 priv = GET_PRIV (window);
1415 chat = priv->current_chat;
1416 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1418 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1422 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1423 chat_window_menu_context_update (priv, num_pages);
1426 static EmpathyChatWindow *
1427 empathy_chat_window_new (void)
1429 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1433 chat_window_detach_activate_cb (GtkAction *action,
1434 EmpathyChatWindow *window)
1436 EmpathyChatWindowPriv *priv;
1437 EmpathyChatWindow *new_window;
1440 priv = GET_PRIV (window);
1442 chat = priv->current_chat;
1443 new_window = empathy_chat_window_new ();
1445 empathy_chat_window_move_chat (window, new_window, chat);
1447 priv = GET_PRIV (new_window);
1448 gtk_widget_show (priv->dialog);
1452 chat_window_help_contents_activate_cb (GtkAction *action,
1453 EmpathyChatWindow *window)
1455 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1457 empathy_url_show (priv->dialog, "help:empathy");
1461 chat_window_help_about_activate_cb (GtkAction *action,
1462 EmpathyChatWindow *window)
1464 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1466 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1470 chat_window_delete_event_cb (GtkWidget *dialog,
1472 EmpathyChatWindow *window)
1474 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1475 EmpathyChat *chat = NULL;
1479 DEBUG ("Delete event received");
1481 for (l = priv->chats; l != NULL; l = l->next) {
1482 if (chat_needs_close_confirmation (l->data)) {
1489 confirm_close (window, TRUE, n_rooms,
1490 (n_rooms == 1 ? chat : NULL));
1492 remove_all_chats (window);
1499 chat_window_composing_cb (EmpathyChat *chat,
1500 gboolean is_composing,
1501 EmpathyChatWindow *window)
1503 chat_window_update_chat_tab (chat);
1507 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1510 EmpathyChatWindowPriv *priv;
1512 priv = GET_PRIV (window);
1514 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1518 chat_window_notification_closed_cb (NotifyNotification *notify,
1519 EmpathyChatWindow *self)
1521 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1523 g_object_unref (notify);
1524 if (priv->notification == notify) {
1525 priv->notification = NULL;
1530 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1531 EmpathyMessage *message,
1534 EmpathyContact *sender;
1535 const gchar *header;
1539 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1540 gboolean res, has_x_canonical_append;
1541 NotifyNotification *notification = priv->notification;
1543 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1546 res = g_settings_get_boolean (priv->gsettings_notif,
1547 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1554 sender = empathy_message_get_sender (message);
1555 header = empathy_contact_get_alias (sender);
1556 body = empathy_message_get_body (message);
1557 escaped = g_markup_escape_text (body, -1);
1558 has_x_canonical_append = empathy_notify_manager_has_capability (
1559 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1561 if (notification != NULL && !has_x_canonical_append) {
1562 /* if the notification server supports x-canonical-append, it is
1563 better to not use notify_notification_update to avoid
1564 overwriting the current notification message */
1565 notify_notification_update (notification,
1566 header, escaped, NULL);
1568 /* if the notification server supports x-canonical-append,
1569 the hint will be added, so that the message from the
1570 just created notification will be automatically appended
1571 to an existing notification with the same title.
1572 In this way the previous message will not be lost: the new
1573 message will appear below it, in the same notification */
1574 notification = notify_notification_new (header, escaped, NULL);
1576 if (priv->notification == NULL) {
1577 priv->notification = notification;
1580 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1582 tp_g_signal_connect_object (notification, "closed",
1583 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1585 if (has_x_canonical_append) {
1586 /* We have to set a not empty string to keep libnotify happy */
1587 notify_notification_set_hint_string (notification,
1588 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1592 const gchar *category = empathy_chat_is_room (chat)
1593 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1594 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1595 notify_notification_set_hint (notification,
1596 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1597 g_variant_new_string (category));
1601 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1602 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1604 if (pixbuf != NULL) {
1605 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1606 g_object_unref (pixbuf);
1609 notify_notification_show (notification, NULL);
1615 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1617 EmpathyChatWindowPriv *priv;
1620 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1622 priv = GET_PRIV (window);
1624 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1630 chat_window_new_message_cb (EmpathyChat *chat,
1631 EmpathyMessage *message,
1633 gboolean should_highlight,
1634 EmpathyChatWindow *window)
1636 EmpathyChatWindowPriv *priv;
1638 gboolean needs_urgency;
1639 EmpathyContact *sender;
1641 priv = GET_PRIV (window);
1643 has_focus = empathy_chat_window_has_focus (window);
1645 /* - if we're the sender, we play the sound if it's specified in the
1646 * preferences and we're not away.
1647 * - if we receive a message, we play the sound if it's specified in the
1648 * preferences and the window does not have focus on the chat receiving
1652 sender = empathy_message_get_sender (message);
1654 if (empathy_contact_is_user (sender)) {
1655 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1656 EMPATHY_SOUND_MESSAGE_OUTGOING);
1659 if (has_focus && priv->current_chat == chat) {
1660 /* window and tab are focused so consider the message to be read */
1662 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1663 empathy_chat_messages_read (chat);
1667 /* Update the chat tab if this is the first unread message */
1668 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1669 chat_window_update_chat_tab (chat);
1672 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1673 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1674 * an unamed MUC (msn-like).
1675 * In case of a MUC, we set urgency if either:
1676 * a) the chatroom's always_urgent property is TRUE
1677 * b) the message contains our alias
1679 if (empathy_chat_is_room (chat)) {
1682 EmpathyChatroom *chatroom;
1684 account = empathy_chat_get_account (chat);
1685 room = empathy_chat_get_id (chat);
1687 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1690 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1691 needs_urgency = TRUE;
1693 needs_urgency = should_highlight;
1696 needs_urgency = TRUE;
1699 if (needs_urgency) {
1701 chat_window_set_urgency_hint (window, TRUE);
1704 /* Pending messages have already been displayed and notified in the
1705 * approver, so we don't display a notification and play a sound for those */
1707 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1708 EMPATHY_SOUND_MESSAGE_INCOMING);
1710 chat_window_show_or_update_notification (window, message, chat);
1714 /* update the number of unread messages and the window icon */
1715 chat_window_title_update (priv);
1716 chat_window_icon_update (priv, TRUE);
1720 chat_window_command_part (EmpathyChat *chat,
1723 EmpathyChat *chat_to_be_parted;
1724 EmpathyTpChat *tp_chat = NULL;
1726 if (strv[1] == NULL) {
1727 /* No chatroom ID specified */
1728 tp_chat = empathy_chat_get_tp_chat (chat);
1730 empathy_tp_chat_leave (tp_chat, "");
1733 chat_to_be_parted = empathy_chat_window_find_chat (
1734 empathy_chat_get_account (chat), strv[1], FALSE);
1736 if (chat_to_be_parted != NULL) {
1737 /* Found a chatroom matching the specified ID */
1738 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1740 empathy_tp_chat_leave (tp_chat, strv[2]);
1744 /* Going by the syntax of PART command:
1746 * /PART [<chatroom-ID>] [<reason>]
1748 * Chatroom-ID is not a must to specify a reason.
1749 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1750 * MUC then the current chatroom should be parted and srtv[1] should
1751 * be treated as part of the optional part-message. */
1752 message = g_strconcat (strv[1], " ", strv[2], NULL);
1753 tp_chat = empathy_chat_get_tp_chat (chat);
1755 empathy_tp_chat_leave (tp_chat, message);
1761 static GtkNotebook *
1762 notebook_create_window_cb (GtkNotebook *source,
1768 EmpathyChatWindowPriv *priv;
1769 EmpathyChatWindow *window, *new_window;
1772 chat = EMPATHY_CHAT (page);
1773 window = chat_window_find_chat (chat);
1775 new_window = empathy_chat_window_new ();
1776 priv = GET_PRIV (new_window);
1778 DEBUG ("Detach hook called");
1780 empathy_chat_window_move_chat (window, new_window, chat);
1782 gtk_widget_show (priv->dialog);
1783 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1789 chat_window_page_switched_cb (GtkNotebook *notebook,
1792 EmpathyChatWindow *window)
1794 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1795 EmpathyChat *chat = EMPATHY_CHAT (child);
1797 DEBUG ("Page switched");
1799 if (priv->page_added) {
1800 priv->page_added = FALSE;
1801 empathy_chat_scroll_down (chat);
1803 else if (priv->current_chat == chat) {
1807 priv->current_chat = chat;
1808 empathy_chat_messages_read (chat);
1810 chat_window_update_chat_tab (chat);
1814 chat_window_page_added_cb (GtkNotebook *notebook,
1817 EmpathyChatWindow *window)
1819 EmpathyChatWindowPriv *priv;
1822 priv = GET_PRIV (window);
1824 /* If we just received DND to the same window, we don't want
1825 * to do anything here like removing the tab and then readding
1826 * it, so we return here and in "page-added".
1828 if (priv->dnd_same_window) {
1829 DEBUG ("Page added (back to the same window)");
1830 priv->dnd_same_window = FALSE;
1834 DEBUG ("Page added");
1836 /* Get chat object */
1837 chat = EMPATHY_CHAT (child);
1839 /* Connect chat signals for this window */
1840 g_signal_connect (chat, "composing",
1841 G_CALLBACK (chat_window_composing_cb),
1843 g_signal_connect (chat, "new-message",
1844 G_CALLBACK (chat_window_new_message_cb),
1846 g_signal_connect (chat, "part-command-entered",
1847 G_CALLBACK (chat_window_command_part),
1849 g_signal_connect (chat, "notify::tp-chat",
1850 G_CALLBACK (chat_window_update_chat_tab),
1853 /* Set flag so we know to perform some special operations on
1854 * switch page due to the new page being added.
1856 priv->page_added = TRUE;
1858 /* Get list of chats up to date */
1859 priv->chats = g_list_append (priv->chats, chat);
1861 chat_window_update_chat_tab (chat);
1865 chat_window_page_removed_cb (GtkNotebook *notebook,
1868 EmpathyChatWindow *window)
1870 EmpathyChatWindowPriv *priv;
1873 priv = GET_PRIV (window);
1875 /* If we just received DND to the same window, we don't want
1876 * to do anything here like removing the tab and then readding
1877 * it, so we return here and in "page-added".
1879 if (priv->dnd_same_window) {
1880 DEBUG ("Page removed (and will be readded to same window)");
1884 DEBUG ("Page removed");
1886 /* Get chat object */
1887 chat = EMPATHY_CHAT (child);
1889 /* Disconnect all signal handlers for this chat and this window */
1890 g_signal_handlers_disconnect_by_func (chat,
1891 G_CALLBACK (chat_window_composing_cb),
1893 g_signal_handlers_disconnect_by_func (chat,
1894 G_CALLBACK (chat_window_new_message_cb),
1896 g_signal_handlers_disconnect_by_func (chat,
1897 G_CALLBACK (chat_window_update_chat_tab),
1900 /* Keep list of chats up to date */
1901 priv->chats = g_list_remove (priv->chats, chat);
1902 empathy_chat_messages_read (chat);
1904 if (priv->chats == NULL) {
1905 g_object_unref (window);
1907 chat_window_update (window, TRUE);
1912 chat_window_focus_in_event_cb (GtkWidget *widget,
1914 EmpathyChatWindow *window)
1916 EmpathyChatWindowPriv *priv;
1918 priv = GET_PRIV (window);
1920 empathy_chat_messages_read (priv->current_chat);
1922 chat_window_set_urgency_hint (window, FALSE);
1924 /* Update the title, since we now mark all unread messages as read. */
1925 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1931 chat_window_drag_drop (GtkWidget *widget,
1932 GdkDragContext *context,
1936 EmpathyChatWindow *window)
1939 EmpathyChatWindowPriv *priv;
1941 priv = GET_PRIV (window);
1943 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1944 if (target == GDK_NONE)
1945 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1947 if (target != GDK_NONE) {
1948 gtk_drag_get_data (widget, context, target, time_);
1956 chat_window_drag_motion (GtkWidget *widget,
1957 GdkDragContext *context,
1961 EmpathyChatWindow *window)
1964 EmpathyChatWindowPriv *priv;
1966 priv = GET_PRIV (window);
1968 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1969 if (target != GDK_NONE) {
1970 /* This is a file drag. Ensure the contact is online and set the
1971 drag type to COPY. Note that it's possible that the tab will
1972 be switched by GTK+ after a timeout from drag_motion without
1973 getting another drag_motion to disable the drop. You have
1974 to hold your mouse really still.
1976 EmpathyContact *contact;
1978 priv = GET_PRIV (window);
1979 contact = empathy_chat_get_remote_contact (priv->current_chat);
1980 /* contact is NULL for multi-user chats. We don't do
1981 * file transfers to MUCs. We also don't send files
1982 * to offline contacts or contacts that don't support
1985 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1986 gdk_drag_status (context, 0, time_);
1989 if (!(empathy_contact_get_capabilities (contact)
1990 & EMPATHY_CAPABILITIES_FT)) {
1991 gdk_drag_status (context, 0, time_);
1994 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1998 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1999 if (target != GDK_NONE) {
2000 /* This is a drag of a contact from a contact list. Set to COPY.
2001 FIXME: If this drag is to a MUC window, it invites the user.
2002 Otherwise, it opens a chat. Should we use a different drag
2003 type for invites? Should we allow ASK?
2005 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2013 drag_data_received_individual_id (EmpathyChatWindow *self,
2015 GdkDragContext *context,
2018 GtkSelectionData *selection,
2023 EmpathyIndividualManager *manager = NULL;
2024 FolksIndividual *individual;
2025 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2026 EmpathyTpChat *chat;
2027 TpContact *tp_contact;
2029 EmpathyContact *contact;
2031 id = (const gchar *) gtk_selection_data_get_data (selection);
2033 DEBUG ("DND invididual %s", id);
2035 if (priv->current_chat == NULL)
2038 chat = empathy_chat_get_tp_chat (priv->current_chat);
2042 if (!empathy_tp_chat_can_add_contact (chat)) {
2043 DEBUG ("Can't invite contact to %s",
2044 tp_proxy_get_object_path (chat));
2048 manager = empathy_individual_manager_dup_singleton ();
2050 individual = empathy_individual_manager_lookup_member (manager, id);
2051 if (individual == NULL) {
2052 DEBUG ("Failed to find individual %s", id);
2056 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2057 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2058 if (tp_contact == NULL) {
2059 DEBUG ("Can't find a TpContact on connection %s for %s",
2060 tp_proxy_get_object_path (conn), id);
2064 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2065 tp_channel_get_identifier ((TpChannel *) chat));
2067 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2068 empathy_tp_chat_add (chat, contact, NULL);
2069 g_object_unref (contact);
2072 gtk_drag_finish (context, TRUE, FALSE, time_);
2073 tp_clear_object (&manager);
2077 chat_window_drag_data_received (GtkWidget *widget,
2078 GdkDragContext *context,
2081 GtkSelectionData *selection,
2084 EmpathyChatWindow *window)
2086 if (info == DND_DRAG_TYPE_CONTACT_ID) {
2087 EmpathyChat *chat = NULL;
2088 EmpathyChatWindow *old_window;
2089 TpAccount *account = NULL;
2090 EmpathyClientFactory *factory;
2093 const gchar *account_id;
2094 const gchar *contact_id;
2096 id = (const gchar*) gtk_selection_data_get_data (selection);
2098 factory = empathy_client_factory_dup ();
2100 DEBUG ("DND contact from roster with id:'%s'", id);
2102 strv = g_strsplit (id, ":", 2);
2103 if (g_strv_length (strv) == 2) {
2104 account_id = strv[0];
2105 contact_id = strv[1];
2107 tp_simple_client_factory_ensure_account (
2108 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2111 g_object_unref (factory);
2112 if (account != NULL)
2113 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2116 if (account == NULL) {
2118 gtk_drag_finish (context, FALSE, FALSE, time_);
2123 empathy_chat_with_contact_id (
2124 account, contact_id,
2125 empathy_get_current_action_time (),
2133 old_window = chat_window_find_chat (chat);
2135 if (old_window == window) {
2136 gtk_drag_finish (context, TRUE, FALSE, time_);
2140 empathy_chat_window_move_chat (old_window, window, chat);
2142 empathy_chat_window_add_chat (window, chat);
2145 /* Added to take care of any outstanding chat events */
2146 empathy_chat_window_present_chat (chat,
2147 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2149 /* We should return TRUE to remove the data when doing
2150 * GDK_ACTION_MOVE, but we don't here otherwise it has
2151 * weird consequences, and we handle that internally
2152 * anyway with add_chat () and remove_chat ().
2154 gtk_drag_finish (context, TRUE, FALSE, time_);
2156 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2157 drag_data_received_individual_id (window, widget, context, x, y,
2158 selection, info, time_);
2160 else if (info == DND_DRAG_TYPE_URI_LIST) {
2161 EmpathyChatWindowPriv *priv;
2162 EmpathyContact *contact;
2165 priv = GET_PRIV (window);
2166 contact = empathy_chat_get_remote_contact (priv->current_chat);
2168 /* contact is NULL when current_chat is a multi-user chat.
2169 * We don't do file transfers to MUCs, so just cancel the drag.
2171 if (contact == NULL) {
2172 gtk_drag_finish (context, TRUE, FALSE, time_);
2176 data = (const gchar *) gtk_selection_data_get_data (selection);
2177 empathy_send_file_from_uri_list (contact, data);
2179 gtk_drag_finish (context, TRUE, FALSE, time_);
2181 else if (info == DND_DRAG_TYPE_TAB) {
2183 EmpathyChatWindow *old_window = NULL;
2187 chat = (void *) gtk_selection_data_get_data (selection);
2188 old_window = chat_window_find_chat (*chat);
2191 EmpathyChatWindowPriv *priv;
2193 priv = GET_PRIV (window);
2194 priv->dnd_same_window = (old_window == window);
2195 DEBUG ("DND tab (within same window: %s)",
2196 priv->dnd_same_window ? "Yes" : "No");
2199 DEBUG ("DND from unknown source");
2200 gtk_drag_finish (context, FALSE, FALSE, time_);
2205 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2206 guint num_chats_in_manager,
2207 EmpathyChatWindow *window)
2209 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2211 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2212 num_chats_in_manager > 0);
2216 chat_window_finalize (GObject *object)
2218 EmpathyChatWindow *window;
2219 EmpathyChatWindowPriv *priv;
2221 window = EMPATHY_CHAT_WINDOW (object);
2222 priv = GET_PRIV (window);
2224 DEBUG ("Finalized: %p", object);
2226 g_object_unref (priv->ui_manager);
2227 g_object_unref (priv->chatroom_manager);
2228 g_object_unref (priv->notify_mgr);
2229 g_object_unref (priv->gsettings_chat);
2230 g_object_unref (priv->gsettings_notif);
2231 g_object_unref (priv->gsettings_ui);
2232 g_object_unref (priv->sound_mgr);
2234 if (priv->notification != NULL) {
2235 notify_notification_close (priv->notification, NULL);
2236 priv->notification = NULL;
2239 if (priv->contact_targets) {
2240 gtk_target_list_unref (priv->contact_targets);
2242 if (priv->file_targets) {
2243 gtk_target_list_unref (priv->file_targets);
2246 if (priv->chat_manager) {
2247 g_signal_handler_disconnect (priv->chat_manager,
2248 priv->chat_manager_chats_changed_id);
2249 g_object_unref (priv->chat_manager);
2250 priv->chat_manager = NULL;
2253 chat_windows = g_list_remove (chat_windows, window);
2254 gtk_widget_destroy (priv->dialog);
2256 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2260 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2262 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2264 object_class->finalize = chat_window_finalize;
2266 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2270 empathy_chat_window_init (EmpathyChatWindow *window)
2273 GtkAccelGroup *accel_group;
2278 GtkWidget *chat_vbox;
2280 EmpathySmileyManager *smiley_manager;
2281 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2282 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2284 window->priv = priv;
2285 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2286 gui = empathy_builder_get_file (filename,
2287 "chat_window", &priv->dialog,
2288 "chat_vbox", &chat_vbox,
2289 "ui_manager", &priv->ui_manager,
2290 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2291 "menu_conv_favorite", &priv->menu_conv_favorite,
2292 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2293 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2294 "menu_edit_cut", &priv->menu_edit_cut,
2295 "menu_edit_copy", &priv->menu_edit_copy,
2296 "menu_edit_paste", &priv->menu_edit_paste,
2297 "menu_edit_find", &priv->menu_edit_find,
2298 "menu_tabs_next", &priv->menu_tabs_next,
2299 "menu_tabs_prev", &priv->menu_tabs_prev,
2300 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2301 "menu_tabs_left", &priv->menu_tabs_left,
2302 "menu_tabs_right", &priv->menu_tabs_right,
2303 "menu_tabs_detach", &priv->menu_tabs_detach,
2307 empathy_builder_connect (gui, window,
2308 "menu_conv", "activate", chat_window_conv_activate_cb,
2309 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2310 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2311 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2312 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2313 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2314 "menu_conv_close", "activate", chat_window_close_activate_cb,
2315 "menu_edit", "activate", chat_window_edit_activate_cb,
2316 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2317 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2318 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2319 "menu_edit_find", "activate", chat_window_find_activate_cb,
2320 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2321 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2322 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2323 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2324 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2325 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2326 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2327 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2330 g_object_ref (priv->ui_manager);
2331 g_object_unref (gui);
2333 empathy_set_css_provider (GTK_WIDGET (priv->dialog));
2335 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2336 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2337 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2338 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2340 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2342 priv->notebook = gtk_notebook_new ();
2344 g_signal_connect (priv->notebook, "create-window",
2345 G_CALLBACK (notebook_create_window_cb), window);
2347 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2348 "EmpathyChatWindow");
2349 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2350 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2351 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2352 gtk_widget_show (priv->notebook);
2355 accel_group = gtk_accel_group_new ();
2356 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2358 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2359 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2362 gtk_accel_group_connect (accel_group,
2369 g_object_unref (accel_group);
2371 /* Set up drag target lists */
2372 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2373 G_N_ELEMENTS (drag_types_dest_contact));
2374 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2375 G_N_ELEMENTS (drag_types_dest_file));
2377 /* Set up smiley menu */
2378 smiley_manager = empathy_smiley_manager_dup_singleton ();
2379 submenu = empathy_smiley_menu_new (smiley_manager,
2380 chat_window_insert_smiley_activate_cb,
2382 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2383 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2384 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2385 g_object_unref (smiley_manager);
2387 /* Set up signals we can't do with ui file since we may need to
2388 * block/unblock them at some later stage.
2391 g_signal_connect (priv->dialog,
2393 G_CALLBACK (chat_window_delete_event_cb),
2395 g_signal_connect (priv->dialog,
2397 G_CALLBACK (chat_window_focus_in_event_cb),
2399 g_signal_connect_after (priv->notebook,
2401 G_CALLBACK (chat_window_page_switched_cb),
2403 g_signal_connect (priv->notebook,
2405 G_CALLBACK (chat_window_page_added_cb),
2407 g_signal_connect (priv->notebook,
2409 G_CALLBACK (chat_window_page_removed_cb),
2412 /* Set up drag and drop */
2413 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2414 GTK_DEST_DEFAULT_HIGHLIGHT,
2416 G_N_ELEMENTS (drag_types_dest),
2417 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2419 /* connect_after to allow GtkNotebook's built-in tab switching */
2420 g_signal_connect_after (priv->notebook,
2422 G_CALLBACK (chat_window_drag_motion),
2424 g_signal_connect (priv->notebook,
2425 "drag-data-received",
2426 G_CALLBACK (chat_window_drag_data_received),
2428 g_signal_connect (priv->notebook,
2430 G_CALLBACK (chat_window_drag_drop),
2433 chat_windows = g_list_prepend (chat_windows, window);
2435 /* Set up private details */
2437 priv->current_chat = NULL;
2438 priv->notification = NULL;
2440 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2442 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2443 priv->chat_manager_chats_changed_id =
2444 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2445 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2448 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2449 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2453 /* Returns the window to open a new tab in if there is a suitable window,
2454 * otherwise, returns NULL indicating that a new window should be added.
2456 static EmpathyChatWindow *
2457 empathy_chat_window_get_default (gboolean room)
2459 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2461 gboolean separate_windows = TRUE;
2463 separate_windows = g_settings_get_boolean (gsettings,
2464 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2466 g_object_unref (gsettings);
2468 if (separate_windows) {
2469 /* Always create a new window */
2473 for (l = chat_windows; l; l = l->next) {
2474 EmpathyChatWindow *chat_window;
2475 guint nb_rooms, nb_private;
2477 chat_window = l->data;
2479 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2481 /* Skip the window if there aren't any rooms in it */
2482 if (room && nb_rooms == 0)
2485 /* Skip the window if there aren't any 1-1 chats in it */
2486 if (!room && nb_private == 0)
2496 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2499 EmpathyChatWindowPriv *priv;
2501 GtkWidget *popup_label;
2503 GValue value = { 0, };
2505 g_return_if_fail (window != NULL);
2506 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2508 priv = GET_PRIV (window);
2510 /* Reference the chat object */
2511 g_object_ref (chat);
2513 /* If this window has just been created, position it */
2514 if (priv->chats == NULL) {
2515 const gchar *name = "chat-window";
2516 gboolean separate_windows;
2518 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2519 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2521 if (empathy_chat_is_room (chat))
2522 name = "room-window";
2524 if (separate_windows) {
2527 /* Save current position of the window */
2528 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2530 /* First bind to the 'generic' name. So new window for which we didn't
2531 * save a geometry yet will have the geometry of the last saved
2532 * window (bgo #601191). */
2533 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2535 /* Restore previous position of the window so the newly created window
2536 * won't be in the same position as the latest saved window and so
2537 * completely hide it. */
2538 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2540 /* Then bind it to the name of the contact/room so we'll save the
2541 * geometry specific to this window */
2542 name = empathy_chat_get_id (chat);
2545 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2548 child = GTK_WIDGET (chat);
2549 label = chat_window_create_label (window, chat, TRUE);
2550 popup_label = chat_window_create_label (window, chat, FALSE);
2551 gtk_widget_show (child);
2553 g_signal_connect (chat, "notify::name",
2554 G_CALLBACK (chat_window_chat_notify_cb),
2556 g_signal_connect (chat, "notify::subject",
2557 G_CALLBACK (chat_window_chat_notify_cb),
2559 g_signal_connect (chat, "notify::remote-contact",
2560 G_CALLBACK (chat_window_chat_notify_cb),
2562 g_signal_connect (chat, "notify::sms-channel",
2563 G_CALLBACK (chat_window_chat_notify_cb),
2565 g_signal_connect (chat, "notify::n-messages-sending",
2566 G_CALLBACK (chat_window_chat_notify_cb),
2568 g_signal_connect (chat, "notify::nb-unread-messages",
2569 G_CALLBACK (chat_window_chat_notify_cb),
2571 chat_window_chat_notify_cb (chat);
2573 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2574 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2575 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2576 g_value_init (&value, G_TYPE_BOOLEAN);
2577 g_value_set_boolean (&value, TRUE);
2578 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2579 child, "tab-expand" , &value);
2580 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2581 child, "tab-fill" , &value);
2582 g_value_unset (&value);
2584 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2588 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2591 EmpathyChatWindowPriv *priv;
2593 EmpathyContact *remote_contact;
2594 EmpathyChatManager *chat_manager;
2596 g_return_if_fail (window != NULL);
2597 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2599 priv = GET_PRIV (window);
2601 g_signal_handlers_disconnect_by_func (chat,
2602 chat_window_chat_notify_cb,
2604 remote_contact = g_object_get_data (G_OBJECT (chat),
2605 "chat-window-remote-contact");
2606 if (remote_contact) {
2607 g_signal_handlers_disconnect_by_func (remote_contact,
2608 chat_window_update_chat_tab,
2612 chat_manager = empathy_chat_manager_dup_singleton ();
2613 empathy_chat_manager_closed_chat (chat_manager, chat);
2614 g_object_unref (chat_manager);
2616 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2618 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2620 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2622 g_object_unref (chat);
2626 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2627 EmpathyChatWindow *new_window,
2632 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2633 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2634 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2636 widget = GTK_WIDGET (chat);
2638 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2639 G_OBJECT (widget)->ref_count);
2641 /* We reference here to make sure we don't loose the widget
2642 * and the EmpathyChat object during the move.
2644 g_object_ref (chat);
2645 g_object_ref (widget);
2647 empathy_chat_window_remove_chat (old_window, chat);
2648 empathy_chat_window_add_chat (new_window, chat);
2650 g_object_unref (widget);
2651 g_object_unref (chat);
2655 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2658 EmpathyChatWindowPriv *priv;
2661 g_return_if_fail (window != NULL);
2662 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2664 priv = GET_PRIV (window);
2666 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2668 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2673 empathy_chat_window_find_chat (TpAccount *account,
2675 gboolean sms_channel)
2679 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2681 for (l = chat_windows; l; l = l->next) {
2682 EmpathyChatWindowPriv *priv;
2683 EmpathyChatWindow *window;
2687 priv = GET_PRIV (window);
2689 for (ll = priv->chats; ll; ll = ll->next) {
2694 if (account == empathy_chat_get_account (chat) &&
2695 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2696 sms_channel == empathy_chat_is_sms_channel (chat)) {
2706 empathy_chat_window_present_chat (EmpathyChat *chat,
2709 EmpathyChatWindow *window;
2710 EmpathyChatWindowPriv *priv;
2711 guint32 x_timestamp;
2713 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2715 window = chat_window_find_chat (chat);
2717 /* If the chat has no window, create one */
2718 if (window == NULL) {
2719 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2721 window = empathy_chat_window_new ();
2723 /* we want to display the newly created window even if we don't present
2725 priv = GET_PRIV (window);
2726 gtk_widget_show (priv->dialog);
2729 empathy_chat_window_add_chat (window, chat);
2732 /* Don't force the window to show itself when it wasn't
2733 * an action by the user
2735 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2738 priv = GET_PRIV (window);
2740 if (x_timestamp != GDK_CURRENT_TIME) {
2741 /* Don't present or switch tab if the action was earlier than the
2742 * last actions X time, accounting for overflow and the first ever
2745 if (priv->x_user_action_time != 0
2746 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2749 priv->x_user_action_time = x_timestamp;
2752 empathy_chat_window_switch_to_chat (window, chat);
2754 /* Don't use empathy_window_present_with_time () which would move the window
2755 * to our current desktop but move to the window's desktop instead. This is
2756 * more coherent with Shell's 'app is ready' notication which moves the view
2757 * to the app desktop rather than moving the app itself. */
2758 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2760 gtk_widget_grab_focus (chat->input_text_view);
2764 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2768 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2770 guint _nb_rooms = 0, _nb_private = 0;
2772 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2773 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2779 if (nb_rooms != NULL)
2780 *nb_rooms = _nb_rooms;
2781 if (nb_private != NULL)
2782 *nb_private = _nb_private;