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>
34 #include "empathy-about-dialog.h"
35 #include "empathy-chat-manager.h"
36 #include "empathy-chatroom-manager.h"
37 #include "empathy-client-factory.h"
38 #include "empathy-geometry.h"
39 #include "empathy-gsettings.h"
40 #include "empathy-images.h"
41 #include "empathy-invite-participant-dialog.h"
42 #include "empathy-notify-manager.h"
43 #include "empathy-request-util.h"
44 #include "empathy-smiley-manager.h"
45 #include "empathy-sound-manager.h"
46 #include "empathy-ui-utils.h"
47 #include "empathy-utils.h"
49 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
50 #include "empathy-debug.h"
52 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
54 #define X_EARLIER_OR_EQL(t1, t2) \
55 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
56 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
61 PROP_INDIVIDUAL_MGR = 1
64 struct _EmpathyChatWindowPriv
66 EmpathyChat *current_chat;
69 gboolean dnd_same_window;
70 EmpathyChatroomManager *chatroom_manager;
71 EmpathyNotifyManager *notify_mgr;
72 EmpathyIndividualManager *individual_mgr;
74 NotifyNotification *notification;
76 GtkTargetList *contact_targets;
77 GtkTargetList *file_targets;
79 EmpathyChatManager *chat_manager;
80 gulong chat_manager_chats_changed_id;
83 GtkUIManager *ui_manager;
84 GtkAction *menu_conv_insert_smiley;
85 GtkAction *menu_conv_favorite;
86 GtkAction *menu_conv_join_chat;
87 GtkAction *menu_conv_leave_chat;
88 GtkAction *menu_conv_always_urgent;
89 GtkAction *menu_conv_toggle_contacts;
91 GtkAction *menu_edit_cut;
92 GtkAction *menu_edit_copy;
93 GtkAction *menu_edit_paste;
94 GtkAction *menu_edit_find;
96 GtkAction *menu_tabs_next;
97 GtkAction *menu_tabs_prev;
98 GtkAction *menu_tabs_undo_close_tab;
99 GtkAction *menu_tabs_left;
100 GtkAction *menu_tabs_right;
101 GtkAction *menu_tabs_detach;
103 /* Last user action time we acted upon to show a tab */
104 guint32 x_user_action_time;
106 GSettings *gsettings_chat;
107 GSettings *gsettings_notif;
108 GSettings *gsettings_ui;
110 EmpathySoundManager *sound_mgr;
112 gboolean updating_menu;
115 static GList *chat_windows = NULL;
117 static const guint tab_accel_keys[] =
119 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
120 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
125 DND_DRAG_TYPE_CONTACT_ID,
126 DND_DRAG_TYPE_INDIVIDUAL_ID,
127 DND_DRAG_TYPE_URI_LIST,
131 static const GtkTargetEntry drag_types_dest[] =
133 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
134 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
135 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
136 /* FIXME: disabled because of bug #640513
137 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
138 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
142 static const GtkTargetEntry drag_types_dest_contact[] =
144 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
145 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
148 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, GTK_TYPE_WINDOW)
176 chat_window_accel_cb (GtkAccelGroup *accelgroup,
180 EmpathyChatWindow *self)
185 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
187 if (tab_accel_keys[i] == key)
195 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
198 static EmpathyChatWindow *
199 chat_window_find_chat (EmpathyChat *chat)
203 for (l = chat_windows; l; l = l->next)
205 EmpathyChatWindow *window = l->data;
207 ll = g_list_find (window->priv->chats, chat);
216 remove_all_chats (EmpathyChatWindow *self)
220 while (self->priv->chats)
221 empathy_chat_window_remove_chat (self, self->priv->chats->data);
223 g_object_unref (self);
227 confirm_close_response_cb (GtkWidget *dialog,
229 EmpathyChatWindow *window)
233 chat = g_object_get_data (G_OBJECT (dialog), "chat");
235 gtk_widget_destroy (dialog);
237 if (response != GTK_RESPONSE_ACCEPT)
241 empathy_chat_window_remove_chat (window, chat);
243 remove_all_chats (window);
247 confirm_close (EmpathyChatWindow *self,
248 gboolean close_window,
253 gchar *primary, *secondary;
255 g_return_if_fail (n_rooms > 0);
258 g_return_if_fail (chat == NULL);
260 g_return_if_fail (chat != NULL);
262 /* If there are no chats in this window, how could we possibly have got
265 g_return_if_fail (self->priv->chats != NULL);
267 /* Treat closing a window which only has one tab exactly like closing
270 if (close_window && self->priv->chats->next == NULL)
272 close_window = FALSE;
273 chat = self->priv->chats->data;
278 primary = g_strdup (_("Close this window?"));
282 gchar *chat_name = empathy_chat_dup_name (chat);
283 secondary = g_strdup_printf (
284 _("Closing this window will leave %s. You will "
285 "not receive any further messages until you "
292 secondary = g_strdup_printf (
293 /* Note to translators: the number of chats will
294 * always be at least 2.
297 "Closing this window will leave a chat room. You will "
298 "not receive any further messages until you rejoin it.",
299 "Closing this window will leave %u chat rooms. You will "
300 "not receive any further messages until you rejoin them.",
307 gchar *chat_name = empathy_chat_dup_name (chat);
308 primary = g_strdup_printf (_("Leave %s?"), chat_name);
309 secondary = g_strdup (
310 _("You will not receive any further messages from this chat "
311 "room until you rejoin it."));
315 dialog = gtk_message_dialog_new (
317 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
322 gtk_window_set_title (GTK_WINDOW (dialog), "");
323 g_object_set (dialog, "secondary-text", secondary, NULL);
328 gtk_dialog_add_button (GTK_DIALOG (dialog),
329 close_window ? _("Close window") : _("Leave room"),
330 GTK_RESPONSE_ACCEPT);
331 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
332 GTK_RESPONSE_ACCEPT);
335 g_object_set_data (G_OBJECT (dialog), "chat", chat);
337 g_signal_connect (dialog, "response",
338 G_CALLBACK (confirm_close_response_cb), self);
340 gtk_window_present (GTK_WINDOW (dialog));
343 /* Returns TRUE if we should check if the user really wants to leave. If it's
344 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
345 * the user is actually in the room as opposed to having been kicked or gone
346 * offline or something), then we should check.
349 chat_needs_close_confirmation (EmpathyChat *chat)
351 return (empathy_chat_is_room (chat) &&
352 empathy_chat_get_tp_chat (chat) != NULL);
356 maybe_close_chat (EmpathyChatWindow *window,
359 g_return_if_fail (chat != NULL);
361 if (chat_needs_close_confirmation (chat))
362 confirm_close (window, FALSE, 1, chat);
364 empathy_chat_window_remove_chat (window, chat);
368 chat_window_close_clicked_cb (GtkAction *action,
371 EmpathyChatWindow *window;
373 window = chat_window_find_chat (chat);
374 maybe_close_chat (window, chat);
378 chat_tab_style_updated_cb (GtkWidget *hbox,
382 int char_width, h, w;
383 PangoContext *context;
384 const PangoFontDescription *font_desc;
385 PangoFontMetrics *metrics;
387 button = g_object_get_data (G_OBJECT (user_data),
388 "chat-window-tab-close-button");
389 context = gtk_widget_get_pango_context (hbox);
391 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
392 GTK_STATE_FLAG_NORMAL);
394 metrics = pango_context_get_metrics (context, font_desc,
395 pango_context_get_language (context));
396 char_width = pango_font_metrics_get_approximate_char_width (metrics);
397 pango_font_metrics_unref (metrics);
399 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
400 GTK_ICON_SIZE_MENU, &w, &h);
402 /* Request at least about 12 chars width plus at least space for the status
403 * image and the close button */
404 gtk_widget_set_size_request (hbox,
405 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
407 gtk_widget_set_size_request (button, w, h);
411 create_close_button (void)
413 GtkWidget *button, *image;
414 GtkStyleContext *context;
416 button = gtk_button_new ();
418 context = gtk_widget_get_style_context (button);
419 gtk_style_context_add_class (context, "empathy-tab-close-button");
421 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
422 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
424 /* We don't want focus/keynav for the button to avoid clutter, and
425 * Ctrl-W works anyway.
427 gtk_widget_set_can_focus (button, FALSE);
428 gtk_widget_set_can_default (button, FALSE);
430 image = gtk_image_new_from_icon_name ("window-close-symbolic",
432 gtk_widget_show (image);
434 gtk_container_add (GTK_CONTAINER (button), image);
440 chat_window_create_label (EmpathyChatWindow *window,
442 gboolean is_tab_label)
445 GtkWidget *name_label;
446 GtkWidget *status_image;
447 GtkWidget *event_box;
448 GtkWidget *event_box_hbox;
449 PangoAttrList *attr_list;
450 PangoAttribute *attr;
452 /* The spacing between the button and the label. */
453 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
455 event_box = gtk_event_box_new ();
456 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
458 name_label = gtk_label_new (NULL);
460 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
462 attr_list = pango_attr_list_new ();
463 attr = pango_attr_scale_new (1/1.2);
464 attr->start_index = 0;
465 attr->end_index = -1;
466 pango_attr_list_insert (attr_list, attr);
467 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
468 pango_attr_list_unref (attr_list);
470 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
471 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
472 g_object_set_data (G_OBJECT (chat),
473 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
476 status_image = gtk_image_new ();
478 /* Spacing between the icon and label. */
479 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
481 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
482 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
484 g_object_set_data (G_OBJECT (chat),
485 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
487 g_object_set_data (G_OBJECT (chat),
488 is_tab_label ? "chat-window-tab-tooltip-widget" :
489 "chat-window-menu-tooltip-widget",
492 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
493 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
497 GtkWidget *close_button;
498 GtkWidget *sending_spinner;
500 sending_spinner = gtk_spinner_new ();
502 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
504 g_object_set_data (G_OBJECT (chat),
505 "chat-window-tab-sending-spinner",
508 close_button = create_close_button ();
509 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
512 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
514 g_signal_connect (close_button,
516 G_CALLBACK (chat_window_close_clicked_cb), chat);
518 /* React to theme changes and also setup the size correctly. */
519 g_signal_connect (hbox, "style-updated",
520 G_CALLBACK (chat_tab_style_updated_cb), chat);
523 gtk_widget_show_all (hbox);
529 _submenu_notify_visible_changed_cb (GObject *object,
533 g_signal_handlers_disconnect_by_func (object,
534 _submenu_notify_visible_changed_cb, userdata);
536 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
540 chat_window_menu_context_update (EmpathyChatWindow *self,
545 gboolean wrap_around;
546 gboolean is_connected;
549 page_num = gtk_notebook_get_current_page (
550 GTK_NOTEBOOK (self->priv->notebook));
551 first_page = (page_num == 0);
552 last_page = (page_num == (num_pages - 1));
553 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
555 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
557 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
559 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
561 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
562 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
563 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
564 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
568 chat_window_conversation_menu_update (EmpathyChatWindow *self)
570 EmpathyTpChat *tp_chat;
571 TpConnection *connection;
573 gboolean sensitive = FALSE;
575 g_return_if_fail (self->priv->current_chat != NULL);
577 action = gtk_ui_manager_get_action (self->priv->ui_manager,
578 "/chats_menubar/menu_conv/menu_conv_invite_participant");
579 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
583 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
585 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
586 (tp_connection_get_status (connection, NULL) ==
587 TP_CONNECTION_STATUS_CONNECTED);
590 gtk_action_set_sensitive (action, sensitive);
594 chat_window_contact_menu_update (EmpathyChatWindow *self)
596 GtkWidget *menu, *submenu, *orig_submenu;
598 if (self->priv->updating_menu)
600 self->priv->updating_menu = TRUE;
602 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
603 "/chats_menubar/menu_contact");
604 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
606 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
608 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
612 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
613 g_object_set_data (G_OBJECT (submenu), "window", self);
615 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
616 gtk_widget_show (menu);
617 gtk_widget_set_sensitive (menu, TRUE);
621 gtk_widget_set_sensitive (menu, FALSE);
626 tp_g_signal_connect_object (orig_submenu,
628 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
631 self->priv->updating_menu = FALSE;
635 get_all_unread_messages (EmpathyChatWindow *self)
640 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
641 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
647 get_window_title_name (EmpathyChatWindow *self)
649 gchar *active_name, *ret;
651 guint current_unread_msgs;
653 nb_chats = g_list_length (self->priv->chats);
654 g_assert (nb_chats > 0);
656 active_name = empathy_chat_dup_name (self->priv->current_chat);
658 current_unread_msgs = empathy_chat_get_nb_unread_messages (
659 self->priv->current_chat);
664 if (current_unread_msgs == 0)
665 ret = g_strdup (active_name);
667 ret = g_strdup_printf (ngettext (
669 "%s (%d unread)", current_unread_msgs),
670 active_name, current_unread_msgs);
674 guint nb_others = nb_chats - 1;
675 guint all_unread_msgs;
677 all_unread_msgs = get_all_unread_messages (self);
679 if (all_unread_msgs == 0)
681 /* no unread message */
682 ret = g_strdup_printf (ngettext (
684 "%s (and %u others)", nb_others),
685 active_name, nb_others);
687 else if (all_unread_msgs == current_unread_msgs)
689 /* unread messages are in the current tab */
690 ret = g_strdup_printf (ngettext (
692 "%s (%d unread)", current_unread_msgs),
693 active_name, current_unread_msgs);
695 else if (current_unread_msgs == 0)
697 /* unread messages are in other tabs */
698 ret = g_strdup_printf (ngettext (
699 "%s (%d unread from others)",
700 "%s (%d unread from others)",
702 active_name, all_unread_msgs);
706 /* unread messages are in all the tabs */
707 ret = g_strdup_printf (ngettext (
708 "%s (%d unread from all)",
709 "%s (%d unread from all)",
711 active_name, all_unread_msgs);
715 g_free (active_name);
721 chat_window_title_update (EmpathyChatWindow *self)
725 name = get_window_title_name (self);
726 gtk_window_set_title (GTK_WINDOW (self), name);
731 chat_window_icon_update (EmpathyChatWindow *self,
732 gboolean new_messages)
735 EmpathyContact *remote_contact;
736 gboolean avatar_in_icon;
739 n_chats = g_list_length (self->priv->chats);
741 /* Update window icon */
744 gtk_window_set_icon_name (GTK_WINDOW (self),
745 EMPATHY_IMAGE_MESSAGE);
749 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
750 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
752 if (n_chats == 1 && avatar_in_icon)
754 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
755 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
757 gtk_window_set_icon (GTK_WINDOW (self), icon);
760 g_object_unref (icon);
764 gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
770 chat_window_close_button_update (EmpathyChatWindow *self,
774 GtkWidget *chat_close_button;
779 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
780 chat_close_button = g_object_get_data (G_OBJECT (chat),
781 "chat-window-tab-close-button");
782 gtk_widget_hide (chat_close_button);
786 for (i=0; i<num_pages; i++)
788 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
789 chat_close_button = g_object_get_data (G_OBJECT (chat),
790 "chat-window-tab-close-button");
791 gtk_widget_show (chat_close_button);
797 chat_window_update (EmpathyChatWindow *self,
798 gboolean update_contact_menu)
802 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
804 /* Update Tab menu */
805 chat_window_menu_context_update (self, num_pages);
807 chat_window_conversation_menu_update (self);
809 /* If this update is due to a focus-in event, we know the menu will be
810 the same as when we last left it, so no work to do. Besides, if we
811 swap out the menu on a focus-in, we may confuse any external global
813 if (update_contact_menu)
815 chat_window_contact_menu_update (self);
818 chat_window_title_update (self);
820 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
822 chat_window_close_button_update (self, num_pages);
826 append_markup_printf (GString *string,
833 va_start (args, format);
835 tmp = g_markup_vprintf_escaped (format, args);
836 g_string_append (string, tmp);
843 chat_window_update_chat_tab_full (EmpathyChat *chat,
844 gboolean update_contact_menu)
846 EmpathyChatWindow *self;
847 EmpathyContact *remote_contact;
851 const gchar *subject;
852 const gchar *status = NULL;
856 const gchar *icon_name;
857 GtkWidget *tab_image;
858 GtkWidget *menu_image;
859 GtkWidget *sending_spinner;
862 self = chat_window_find_chat (chat);
866 /* Get information */
867 name = empathy_chat_dup_name (chat);
868 account = empathy_chat_get_account (chat);
869 subject = empathy_chat_get_subject (chat);
870 remote_contact = empathy_chat_get_remote_contact (chat);
872 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
874 name, tp_proxy_get_object_path (account), subject, remote_contact);
876 /* Update tab image */
877 if (empathy_chat_get_tp_chat (chat) == NULL)
879 /* No TpChat, we are disconnected */
882 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
884 icon_name = EMPATHY_IMAGE_MESSAGE;
886 else if (remote_contact && empathy_chat_is_composing (chat))
888 icon_name = EMPATHY_IMAGE_TYPING;
890 else if (empathy_chat_is_sms_channel (chat))
892 icon_name = EMPATHY_IMAGE_SMS;
894 else if (remote_contact)
896 icon_name = empathy_icon_name_for_contact (remote_contact);
900 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
903 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
904 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
906 if (icon_name != NULL)
908 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
910 gtk_widget_show (tab_image);
911 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
913 gtk_widget_show (menu_image);
917 gtk_widget_hide (tab_image);
918 gtk_widget_hide (menu_image);
921 /* Update the sending spinner */
922 nb_sending = empathy_chat_get_n_messages_sending (chat);
923 sending_spinner = g_object_get_data (G_OBJECT (chat),
924 "chat-window-tab-sending-spinner");
926 g_object_set (sending_spinner,
927 "active", nb_sending > 0,
928 "visible", nb_sending > 0,
931 /* Update tab tooltip */
932 tooltip = g_string_new (NULL);
936 id = empathy_contact_get_id (remote_contact);
937 status = empathy_contact_get_presence_message (remote_contact);
944 if (empathy_chat_is_sms_channel (chat))
945 append_markup_printf (tooltip, "%s ", _("SMS:"));
947 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
948 id, tp_account_get_display_name (account));
952 char *tmp = g_strdup_printf (
953 ngettext ("Sending %d message",
954 "Sending %d messages",
958 g_string_append (tooltip, "\n");
959 g_string_append (tooltip, tmp);
961 gtk_widget_set_tooltip_text (sending_spinner, tmp);
965 if (!EMP_STR_EMPTY (status))
966 append_markup_printf (tooltip, "\n<i>%s</i>", status);
968 if (!EMP_STR_EMPTY (subject))
969 append_markup_printf (tooltip, "\n<b>%s</b> %s",
970 _("Topic:"), subject);
972 if (remote_contact && empathy_chat_is_composing (chat))
973 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
975 if (remote_contact != NULL)
977 const gchar * const *types;
979 types = empathy_contact_get_client_types (remote_contact);
980 if (empathy_client_types_contains_mobile_device ((GStrv) types))
982 /* I'm on a mobile device ! */
985 name = g_strdup_printf ("☎ %s", name);
990 markup = g_string_free (tooltip, FALSE);
991 widget = g_object_get_data (G_OBJECT (chat),
992 "chat-window-tab-tooltip-widget");
993 gtk_widget_set_tooltip_markup (widget, markup);
995 widget = g_object_get_data (G_OBJECT (chat),
996 "chat-window-menu-tooltip-widget");
997 gtk_widget_set_tooltip_markup (widget, markup);
1000 /* Update tab and menu label */
1001 if (empathy_chat_is_highlighted (chat))
1003 markup = g_markup_printf_escaped (
1004 "<span color=\"red\" weight=\"bold\">%s</span>",
1009 markup = g_markup_escape_text (name, -1);
1012 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1013 gtk_label_set_markup (GTK_LABEL (widget), markup);
1014 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1015 gtk_label_set_markup (GTK_LABEL (widget), markup);
1018 /* Update the window if it's the current chat */
1019 if (self->priv->current_chat == chat)
1020 chat_window_update (self, update_contact_menu);
1026 chat_window_update_chat_tab (EmpathyChat *chat)
1028 chat_window_update_chat_tab_full (chat, TRUE);
1032 chat_window_chat_notify_cb (EmpathyChat *chat)
1034 EmpathyChatWindow *window;
1035 EmpathyContact *old_remote_contact;
1036 EmpathyContact *remote_contact = NULL;
1038 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1039 "chat-window-remote-contact");
1040 remote_contact = empathy_chat_get_remote_contact (chat);
1042 if (old_remote_contact != remote_contact)
1044 /* The remote-contact associated with the chat changed, we need
1045 * to keep track of any change of that contact and update the
1046 * window each time. */
1048 g_signal_connect_swapped (remote_contact, "notify",
1049 G_CALLBACK (chat_window_update_chat_tab), chat);
1051 if (old_remote_contact)
1052 g_signal_handlers_disconnect_by_func (old_remote_contact,
1053 chat_window_update_chat_tab, chat);
1055 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1056 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1059 chat_window_update_chat_tab (chat);
1061 window = chat_window_find_chat (chat);
1063 chat_window_update (window, FALSE);
1067 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1068 EmpathySmiley *smiley,
1071 EmpathyChatWindow *self = user_data;
1073 GtkTextBuffer *buffer;
1076 chat = self->priv->current_chat;
1078 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1079 gtk_text_buffer_get_end_iter (buffer, &iter);
1080 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1084 chat_window_conv_activate_cb (GtkAction *action,
1085 EmpathyChatWindow *self)
1089 EmpathyContact *remote_contact = NULL;
1090 gboolean disconnected;
1092 /* Favorite room menu */
1093 is_room = empathy_chat_is_room (self->priv->current_chat);
1098 gboolean found = FALSE;
1099 EmpathyChatroom *chatroom;
1101 room = empathy_chat_get_id (self->priv->current_chat);
1102 account = empathy_chat_get_account (self->priv->current_chat);
1103 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1106 if (chatroom != NULL)
1107 found = empathy_chatroom_is_favorite (chatroom);
1109 DEBUG ("This room %s favorite", found ? "is" : "is not");
1110 gtk_toggle_action_set_active (
1111 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1113 if (chatroom != NULL)
1114 found = empathy_chatroom_is_always_urgent (chatroom);
1116 gtk_toggle_action_set_active (
1117 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1120 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1121 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1123 /* Show contacts menu */
1124 g_object_get (self->priv->current_chat,
1125 "remote-contact", &remote_contact,
1126 "show-contacts", &active,
1129 if (remote_contact == NULL)
1131 gtk_toggle_action_set_active (
1132 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1135 /* Menu-items to be visible for MUCs only */
1136 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1137 (remote_contact == NULL));
1139 disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1142 gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1143 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1147 TpChannel *channel = NULL;
1148 TpContact *self_contact = NULL;
1149 TpHandle self_handle = 0;
1151 channel = (TpChannel *) (empathy_chat_get_tp_chat (
1152 self->priv->current_chat));
1153 self_contact = tp_channel_group_get_self_contact (channel);
1154 if (self_contact == NULL)
1156 /* The channel may not be a group */
1157 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1161 self_handle = tp_contact_get_handle (self_contact);
1162 /* There is sometimes a lag between the members-changed signal
1163 emitted on tp-chat and invalidated signal being emitted on the channel.
1164 Leave Chat menu-item should be sensitive only till our self-handle is
1165 a part of channel-members */
1166 gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1170 /* Join Chat is insensitive for a connected chat */
1171 gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1174 if (remote_contact != NULL)
1175 g_object_unref (remote_contact);
1179 chat_window_clear_activate_cb (GtkAction *action,
1180 EmpathyChatWindow *self)
1182 empathy_chat_clear (self->priv->current_chat);
1186 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1187 EmpathyChatWindow *self)
1193 EmpathyChatroom *chatroom;
1195 active = gtk_toggle_action_get_active (toggle_action);
1196 account = empathy_chat_get_account (self->priv->current_chat);
1197 room = empathy_chat_get_id (self->priv->current_chat);
1198 name = empathy_chat_dup_name (self->priv->current_chat);
1200 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1201 account, room, name);
1203 empathy_chatroom_set_favorite (chatroom, active);
1204 g_object_unref (chatroom);
1209 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1210 EmpathyChatWindow *self)
1216 EmpathyChatroom *chatroom;
1218 active = gtk_toggle_action_get_active (toggle_action);
1219 account = empathy_chat_get_account (self->priv->current_chat);
1220 room = empathy_chat_get_id (self->priv->current_chat);
1221 name = empathy_chat_dup_name (self->priv->current_chat);
1223 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1224 account, room, name);
1226 empathy_chatroom_set_always_urgent (chatroom, active);
1227 g_object_unref (chatroom);
1232 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1233 EmpathyChatWindow *self)
1237 active = gtk_toggle_action_get_active (toggle_action);
1239 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1243 chat_window_invite_participant_activate_cb (GtkAction *action,
1244 EmpathyChatWindow *self)
1247 EmpathyTpChat *tp_chat;
1250 g_return_if_fail (self->priv->current_chat != NULL);
1252 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1254 dialog = empathy_invite_participant_dialog_new (
1255 GTK_WINDOW (self), tp_chat);
1257 gtk_widget_show (dialog);
1259 response = gtk_dialog_run (GTK_DIALOG (dialog));
1261 if (response == GTK_RESPONSE_ACCEPT)
1263 TpContact *tp_contact;
1264 EmpathyContact *contact;
1266 tp_contact = empathy_invite_participant_dialog_get_selected (
1267 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1268 if (tp_contact == NULL)
1271 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1273 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1275 g_object_unref (contact);
1279 gtk_widget_destroy (dialog);
1283 chat_window_join_chat_activate_cb (GtkAction *action,
1284 EmpathyChatWindow *self)
1286 g_return_if_fail (self->priv->current_chat != NULL);
1288 empathy_chat_join_muc (self->priv->current_chat,
1289 empathy_chat_get_id (self->priv->current_chat));
1293 chat_window_leave_chat_activate_cb (GtkAction *action,
1294 EmpathyChatWindow *self)
1296 EmpathyTpChat * tp_chat;
1298 g_return_if_fail (self->priv->current_chat != NULL);
1300 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1301 if (tp_chat != NULL)
1302 empathy_tp_chat_leave (tp_chat, "");
1306 chat_window_close_activate_cb (GtkAction *action,
1307 EmpathyChatWindow *self)
1309 g_return_if_fail (self->priv->current_chat != NULL);
1311 maybe_close_chat (self, self->priv->current_chat);
1315 chat_window_edit_activate_cb (GtkAction *action,
1316 EmpathyChatWindow *self)
1318 GtkClipboard *clipboard;
1319 GtkTextBuffer *buffer;
1320 gboolean text_available;
1322 g_return_if_fail (self->priv->current_chat != NULL);
1324 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1326 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1327 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1328 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1332 buffer = gtk_text_view_get_buffer (
1333 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1335 if (gtk_text_buffer_get_has_selection (buffer))
1337 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1338 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1344 selection = empathy_theme_adium_get_has_selection (
1345 self->priv->current_chat->view);
1347 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1348 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1351 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1352 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1353 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1357 chat_window_cut_activate_cb (GtkAction *action,
1358 EmpathyChatWindow *self)
1360 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1362 empathy_chat_cut (self->priv->current_chat);
1366 chat_window_copy_activate_cb (GtkAction *action,
1367 EmpathyChatWindow *self)
1369 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1371 empathy_chat_copy (self->priv->current_chat);
1375 chat_window_paste_activate_cb (GtkAction *action,
1376 EmpathyChatWindow *self)
1378 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1380 empathy_chat_paste (self->priv->current_chat);
1384 chat_window_find_activate_cb (GtkAction *action,
1385 EmpathyChatWindow *self)
1387 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1389 empathy_chat_find (self->priv->current_chat);
1393 chat_window_tabs_next_activate_cb (GtkAction *action,
1394 EmpathyChatWindow *self)
1396 gint index_, numPages;
1397 gboolean wrap_around;
1399 g_object_get (gtk_settings_get_default (),
1400 "gtk-keynav-wrap-around", &wrap_around,
1403 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1404 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1406 if (index_ == (numPages - 1) && wrap_around)
1408 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1412 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1416 chat_window_tabs_previous_activate_cb (GtkAction *action,
1417 EmpathyChatWindow *self)
1419 gint index_, numPages;
1420 gboolean wrap_around;
1422 g_object_get (gtk_settings_get_default (),
1423 "gtk-keynav-wrap-around", &wrap_around,
1426 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1427 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1429 if (index_ <= 0 && wrap_around)
1431 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1436 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1440 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1441 EmpathyChatWindow *self)
1443 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1444 empathy_get_current_action_time ());
1448 chat_window_tabs_left_activate_cb (GtkAction *action,
1449 EmpathyChatWindow *self)
1452 gint index_, num_pages;
1454 chat = self->priv->current_chat;
1455 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1459 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1462 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1463 chat_window_menu_context_update (self, num_pages);
1467 chat_window_tabs_right_activate_cb (GtkAction *action,
1468 EmpathyChatWindow *self)
1471 gint index_, num_pages;
1473 chat = self->priv->current_chat;
1474 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1476 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1479 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1480 chat_window_menu_context_update (self, num_pages);
1483 static EmpathyChatWindow *
1484 empathy_chat_window_new (void)
1486 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1487 "default-width", 580,
1488 "default-height", 480,
1495 chat_window_detach_activate_cb (GtkAction *action,
1496 EmpathyChatWindow *self)
1498 EmpathyChatWindow *new_window;
1501 chat = self->priv->current_chat;
1502 new_window = empathy_chat_window_new ();
1504 empathy_chat_window_move_chat (self, new_window, chat);
1506 gtk_widget_show (GTK_WIDGET (new_window));
1510 chat_window_help_contents_activate_cb (GtkAction *action,
1511 EmpathyChatWindow *self)
1513 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1517 chat_window_help_about_activate_cb (GtkAction *action,
1518 EmpathyChatWindow *self)
1520 empathy_about_dialog_new (GTK_WINDOW (self));
1524 chat_window_delete_event_cb (GtkWidget *dialog,
1526 EmpathyChatWindow *self)
1528 EmpathyChat *chat = NULL;
1532 DEBUG ("Delete event received");
1534 for (l = self->priv->chats; l != NULL; l = l->next)
1536 if (chat_needs_close_confirmation (l->data))
1545 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1549 remove_all_chats (self);
1556 chat_window_composing_cb (EmpathyChat *chat,
1557 gboolean is_composing,
1558 EmpathyChatWindow *self)
1560 chat_window_update_chat_tab (chat);
1564 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1567 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1571 chat_window_notification_closed_cb (NotifyNotification *notify,
1572 EmpathyChatWindow *self)
1574 g_object_unref (notify);
1575 if (self->priv->notification == notify)
1576 self->priv->notification = NULL;
1580 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1581 EmpathyMessage *message,
1584 EmpathyContact *sender;
1585 const gchar *header;
1589 gboolean res, has_x_canonical_append;
1590 NotifyNotification *notification = self->priv->notification;
1592 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1595 res = g_settings_get_boolean (self->priv->gsettings_notif,
1596 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1601 sender = empathy_message_get_sender (message);
1602 header = empathy_contact_get_alias (sender);
1603 body = empathy_message_get_body (message);
1604 escaped = g_markup_escape_text (body, -1);
1606 has_x_canonical_append = empathy_notify_manager_has_capability (
1607 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1609 if (notification != NULL && !has_x_canonical_append)
1611 /* if the notification server supports x-canonical-append, it is
1612 better to not use notify_notification_update to avoid
1613 overwriting the current notification message */
1614 notify_notification_update (notification,
1615 header, escaped, NULL);
1619 /* if the notification server supports x-canonical-append,
1620 the hint will be added, so that the message from the
1621 just created notification will be automatically appended
1622 to an existing notification with the same title.
1623 In this way the previous message will not be lost: the new
1624 message will appear below it, in the same notification */
1625 const gchar *category = empathy_chat_is_room (chat)
1626 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1627 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1629 notification = empathy_notify_manager_create_notification (header,
1632 if (self->priv->notification == NULL)
1633 self->priv->notification = notification;
1635 tp_g_signal_connect_object (notification, "closed",
1636 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1638 if (has_x_canonical_append)
1640 /* We have to set a not empty string to keep libnotify happy */
1641 notify_notification_set_hint_string (notification,
1642 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1645 notify_notification_set_hint (notification,
1646 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1649 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1650 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1654 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1655 g_object_unref (pixbuf);
1658 notify_notification_show (notification, NULL);
1664 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1668 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1670 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1676 chat_window_new_message_cb (EmpathyChat *chat,
1677 EmpathyMessage *message,
1679 gboolean should_highlight,
1680 EmpathyChatWindow *self)
1683 gboolean needs_urgency;
1684 EmpathyContact *sender;
1686 has_focus = empathy_chat_window_has_focus (self);
1688 /* - if we're the sender, we play the sound if it's specified in the
1689 * preferences and we're not away.
1690 * - if we receive a message, we play the sound if it's specified in the
1691 * preferences and the window does not have focus on the chat receiving
1695 sender = empathy_message_get_sender (message);
1697 if (empathy_contact_is_user (sender))
1699 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1700 EMPATHY_SOUND_MESSAGE_OUTGOING);
1704 if (has_focus && self->priv->current_chat == chat)
1706 /* window and tab are focused so consider the message to be read */
1708 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1709 empathy_chat_messages_read (chat);
1713 /* Update the chat tab if this is the first unread message */
1714 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1716 chat_window_update_chat_tab (chat);
1719 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1720 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1721 * an unamed MUC (msn-like).
1722 * In case of a MUC, we set urgency if either:
1723 * a) the chatroom's always_urgent property is TRUE
1724 * b) the message contains our alias
1726 if (empathy_chat_is_room (chat))
1730 EmpathyChatroom *chatroom;
1732 account = empathy_chat_get_account (chat);
1733 room = empathy_chat_get_id (chat);
1735 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1738 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1739 needs_urgency = TRUE;
1741 needs_urgency = should_highlight;
1745 needs_urgency = TRUE;
1751 chat_window_set_urgency_hint (self, TRUE);
1753 /* Pending messages have already been displayed and notified in the
1754 * approver, so we don't display a notification and play a sound
1758 empathy_sound_manager_play (self->priv->sound_mgr,
1759 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1761 chat_window_show_or_update_notification (self, message, chat);
1765 /* update the number of unread messages and the window icon */
1766 chat_window_title_update (self);
1767 chat_window_icon_update (self, TRUE);
1771 chat_window_command_part (EmpathyChat *chat,
1774 EmpathyChat *chat_to_be_parted;
1775 EmpathyTpChat *tp_chat = NULL;
1777 if (strv[1] == NULL)
1779 /* No chatroom ID specified */
1780 tp_chat = empathy_chat_get_tp_chat (chat);
1783 empathy_tp_chat_leave (tp_chat, "");
1788 chat_to_be_parted = empathy_chat_window_find_chat (
1789 empathy_chat_get_account (chat), strv[1], FALSE);
1791 if (chat_to_be_parted != NULL)
1793 /* Found a chatroom matching the specified ID */
1794 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1797 empathy_tp_chat_leave (tp_chat, strv[2]);
1803 /* Going by the syntax of PART command:
1805 * /PART [<chatroom-ID>] [<reason>]
1807 * Chatroom-ID is not a must to specify a reason.
1808 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1809 * MUC then the current chatroom should be parted and srtv[1] should
1810 * be treated as part of the optional part-message. */
1811 message = g_strconcat (strv[1], " ", strv[2], NULL);
1812 tp_chat = empathy_chat_get_tp_chat (chat);
1815 empathy_tp_chat_leave (tp_chat, message);
1821 static GtkNotebook *
1822 notebook_create_window_cb (GtkNotebook *source,
1828 EmpathyChatWindow *window, *new_window;
1831 chat = EMPATHY_CHAT (page);
1832 window = chat_window_find_chat (chat);
1834 new_window = empathy_chat_window_new ();
1836 DEBUG ("Detach hook called");
1838 empathy_chat_window_move_chat (window, new_window, chat);
1840 gtk_widget_show (GTK_WIDGET (new_window));
1841 gtk_window_move (GTK_WINDOW (new_window), x, y);
1847 chat_window_page_switched_cb (GtkNotebook *notebook,
1850 EmpathyChatWindow *self)
1852 EmpathyChat *chat = EMPATHY_CHAT (child);
1854 DEBUG ("Page switched");
1856 if (self->priv->page_added)
1858 self->priv->page_added = FALSE;
1859 empathy_chat_scroll_down (chat);
1861 else if (self->priv->current_chat == chat)
1866 self->priv->current_chat = chat;
1867 empathy_chat_messages_read (chat);
1869 chat_window_update_chat_tab (chat);
1873 chat_window_page_added_cb (GtkNotebook *notebook,
1876 EmpathyChatWindow *self)
1880 /* If we just received DND to the same window, we don't want
1881 * to do anything here like removing the tab and then readding
1882 * it, so we return here and in "page-added".
1884 if (self->priv->dnd_same_window)
1886 DEBUG ("Page added (back to the same window)");
1887 self->priv->dnd_same_window = FALSE;
1891 DEBUG ("Page added");
1893 /* Get chat object */
1894 chat = EMPATHY_CHAT (child);
1896 /* Connect chat signals for this window */
1897 g_signal_connect (chat, "composing",
1898 G_CALLBACK (chat_window_composing_cb), self);
1899 g_signal_connect (chat, "new-message",
1900 G_CALLBACK (chat_window_new_message_cb), self);
1901 g_signal_connect (chat, "part-command-entered",
1902 G_CALLBACK (chat_window_command_part), NULL);
1903 g_signal_connect (chat, "notify::tp-chat",
1904 G_CALLBACK (chat_window_update_chat_tab), self);
1906 /* Set flag so we know to perform some special operations on
1907 * switch page due to the new page being added.
1909 self->priv->page_added = TRUE;
1911 /* Get list of chats up to date */
1912 self->priv->chats = g_list_append (self->priv->chats, chat);
1914 chat_window_update_chat_tab (chat);
1918 chat_window_page_removed_cb (GtkNotebook *notebook,
1921 EmpathyChatWindow *self)
1925 /* If we just received DND to the same window, we don't want
1926 * to do anything here like removing the tab and then readding
1927 * it, so we return here and in "page-added".
1929 if (self->priv->dnd_same_window)
1931 DEBUG ("Page removed (and will be readded to same window)");
1935 DEBUG ("Page removed");
1937 /* Get chat object */
1938 chat = EMPATHY_CHAT (child);
1940 /* Disconnect all signal handlers for this chat and this window */
1941 g_signal_handlers_disconnect_by_func (chat,
1942 G_CALLBACK (chat_window_composing_cb), self);
1943 g_signal_handlers_disconnect_by_func (chat,
1944 G_CALLBACK (chat_window_new_message_cb), self);
1945 g_signal_handlers_disconnect_by_func (chat,
1946 G_CALLBACK (chat_window_update_chat_tab), self);
1948 /* Keep list of chats up to date */
1949 self->priv->chats = g_list_remove (self->priv->chats, chat);
1950 empathy_chat_messages_read (chat);
1952 if (self->priv->chats == NULL)
1954 gtk_widget_destroy (GTK_WIDGET (self));
1958 chat_window_update (self, TRUE);
1963 chat_window_focus_in_event_cb (GtkWidget *widget,
1965 EmpathyChatWindow *self)
1967 empathy_chat_messages_read (self->priv->current_chat);
1969 chat_window_set_urgency_hint (self, FALSE);
1971 /* Update the title, since we now mark all unread messages as read. */
1972 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1978 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1979 EmpathyChatWindow *self)
1981 chat_window_contact_menu_update (self);
1985 chat_window_focus_out_event_cb (GtkWidget *widget,
1987 EmpathyChatWindow *self)
1989 if (self->priv->individual_mgr != NULL)
1992 /* Keep the individual manager alive so we won't fetch everything from Folks
1993 * each time we need to use it. Loading FolksAggregator can takes quite a
1994 * while (if user has a huge LDAP abook for example) and it blocks
1995 * the mainloop during most of this loading. We workaround this by loading
1996 * it when the chat window has been unfocused and so, hopefully, not impact
1997 * the reactivity of the chat window too much.
1999 * The individual manager (and so Folks) is needed to know to which
2000 * FolksIndividual a TpContact belongs, including:
2001 * - empathy_chat_get_contact_menu: to list all the personas of the contact
2002 * - empathy_display_individual_info: to invoke gnome-contacts with the
2003 * FolksIndividual.id of the contact
2004 * - drag_data_received_individual_id: to find the individual associated
2005 * with the ID we received from the DnD in order to invite him.
2007 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2009 if (!empathy_individual_manager_get_contacts_loaded (
2010 self->priv->individual_mgr))
2012 /* We want to update the contact menu when Folks is loaded so we can
2013 * list all the personas of the contact. */
2014 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2015 G_CALLBACK (contacts_loaded_cb), self, 0);
2018 g_object_notify (G_OBJECT (self), "individual-manager");
2024 chat_window_drag_drop (GtkWidget *widget,
2025 GdkDragContext *context,
2029 EmpathyChatWindow *self)
2033 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2034 if (target == GDK_NONE)
2035 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2037 if (target != GDK_NONE)
2039 gtk_drag_get_data (widget, context, target, time_);
2047 chat_window_drag_motion (GtkWidget *widget,
2048 GdkDragContext *context,
2052 EmpathyChatWindow *self)
2056 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2058 if (target != GDK_NONE)
2060 /* This is a file drag. Ensure the contact is online and set the
2061 drag type to COPY. Note that it's possible that the tab will
2062 be switched by GTK+ after a timeout from drag_motion without
2063 getting another drag_motion to disable the drop. You have
2064 to hold your mouse really still.
2066 EmpathyContact *contact;
2068 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2070 /* contact is NULL for multi-user chats. We don't do
2071 * file transfers to MUCs. We also don't send files
2072 * to offline contacts or contacts that don't support
2075 if ((contact == NULL) || !empathy_contact_is_online (contact))
2077 gdk_drag_status (context, 0, time_);
2081 if (!(empathy_contact_get_capabilities (contact)
2082 & EMPATHY_CAPABILITIES_FT))
2084 gdk_drag_status (context, 0, time_);
2088 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2092 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2093 if (target != GDK_NONE)
2095 /* This is a drag of a contact from a contact list. Set to COPY.
2096 FIXME: If this drag is to a MUC window, it invites the user.
2097 Otherwise, it opens a chat. Should we use a different drag
2098 type for invites? Should we allow ASK?
2100 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2108 drag_data_received_individual_id (EmpathyChatWindow *self,
2110 GdkDragContext *context,
2113 GtkSelectionData *selection,
2118 FolksIndividual *individual;
2119 EmpathyTpChat *chat;
2120 TpContact *tp_contact;
2122 EmpathyContact *contact;
2124 id = (const gchar *) gtk_selection_data_get_data (selection);
2126 DEBUG ("DND invididual %s", id);
2128 if (self->priv->current_chat == NULL)
2131 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2135 if (!empathy_tp_chat_can_add_contact (chat))
2137 DEBUG ("Can't invite contact to %s",
2138 tp_proxy_get_object_path (chat));
2142 if (self->priv->individual_mgr == NULL)
2143 /* Not likely as we have to focus out the chat window in order to start
2144 * the DnD but best to be safe. */
2147 individual = empathy_individual_manager_lookup_member (
2148 self->priv->individual_mgr, id);
2149 if (individual == NULL)
2151 DEBUG ("Failed to find individual %s", id);
2155 conn = tp_channel_get_connection ((TpChannel *) chat);
2156 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2157 if (tp_contact == NULL)
2159 DEBUG ("Can't find a TpContact on connection %s for %s",
2160 tp_proxy_get_object_path (conn), id);
2164 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2165 tp_channel_get_identifier ((TpChannel *) chat));
2167 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2168 empathy_tp_chat_add (chat, contact, NULL);
2169 g_object_unref (contact);
2172 gtk_drag_finish (context, TRUE, FALSE, time_);
2176 chat_window_drag_data_received (GtkWidget *widget,
2177 GdkDragContext *context,
2180 GtkSelectionData *selection,
2183 EmpathyChatWindow *self)
2185 if (info == DND_DRAG_TYPE_CONTACT_ID)
2187 EmpathyChat *chat = NULL;
2188 EmpathyChatWindow *old_window;
2189 TpAccount *account = NULL;
2190 EmpathyClientFactory *factory;
2193 const gchar *account_id;
2194 const gchar *contact_id;
2196 id = (const gchar*) gtk_selection_data_get_data (selection);
2198 factory = empathy_client_factory_dup ();
2200 DEBUG ("DND contact from roster with id:'%s'", id);
2202 strv = g_strsplit (id, ":", 2);
2203 if (g_strv_length (strv) == 2)
2205 account_id = strv[0];
2206 contact_id = strv[1];
2208 account = tp_simple_client_factory_ensure_account (
2209 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2211 g_object_unref (factory);
2212 if (account != NULL)
2213 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2216 if (account == NULL)
2219 gtk_drag_finish (context, FALSE, FALSE, time_);
2225 empathy_chat_with_contact_id (account, contact_id,
2226 empathy_get_current_action_time (), NULL, NULL);
2234 old_window = chat_window_find_chat (chat);
2237 if (old_window == self)
2239 gtk_drag_finish (context, TRUE, FALSE, time_);
2243 empathy_chat_window_move_chat (old_window, self, chat);
2247 empathy_chat_window_add_chat (self, chat);
2250 /* Added to take care of any outstanding chat events */
2251 empathy_chat_window_present_chat (chat,
2252 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2254 /* We should return TRUE to remove the data when doing
2255 * GDK_ACTION_MOVE, but we don't here otherwise it has
2256 * weird consequences, and we handle that internally
2257 * anyway with add_chat () and remove_chat ().
2259 gtk_drag_finish (context, TRUE, FALSE, time_);
2261 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2263 drag_data_received_individual_id (self, widget, context, x, y,
2264 selection, info, time_);
2266 else if (info == DND_DRAG_TYPE_URI_LIST)
2268 EmpathyContact *contact;
2271 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2273 /* contact is NULL when current_chat is a multi-user chat.
2274 * We don't do file transfers to MUCs, so just cancel the drag.
2276 if (contact == NULL)
2278 gtk_drag_finish (context, TRUE, FALSE, time_);
2282 data = (const gchar *) gtk_selection_data_get_data (selection);
2283 empathy_send_file_from_uri_list (contact, data);
2285 gtk_drag_finish (context, TRUE, FALSE, time_);
2287 else if (info == DND_DRAG_TYPE_TAB)
2290 EmpathyChatWindow *old_window = NULL;
2294 chat = (void *) gtk_selection_data_get_data (selection);
2295 old_window = chat_window_find_chat (*chat);
2299 self->priv->dnd_same_window = (old_window == self);
2301 DEBUG ("DND tab (within same window: %s)",
2302 self->priv->dnd_same_window ? "Yes" : "No");
2307 DEBUG ("DND from unknown source");
2308 gtk_drag_finish (context, FALSE, FALSE, time_);
2313 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2314 guint num_chats_in_manager,
2315 EmpathyChatWindow *self)
2317 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2318 num_chats_in_manager > 0);
2322 chat_window_finalize (GObject *object)
2324 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2326 DEBUG ("Finalized: %p", object);
2328 g_object_unref (self->priv->ui_manager);
2329 g_object_unref (self->priv->chatroom_manager);
2330 g_object_unref (self->priv->notify_mgr);
2331 g_object_unref (self->priv->gsettings_chat);
2332 g_object_unref (self->priv->gsettings_notif);
2333 g_object_unref (self->priv->gsettings_ui);
2334 g_object_unref (self->priv->sound_mgr);
2335 g_clear_object (&self->priv->individual_mgr);
2337 if (self->priv->notification != NULL)
2339 notify_notification_close (self->priv->notification, NULL);
2340 self->priv->notification = NULL;
2343 if (self->priv->contact_targets)
2344 gtk_target_list_unref (self->priv->contact_targets);
2346 if (self->priv->file_targets)
2347 gtk_target_list_unref (self->priv->file_targets);
2349 if (self->priv->chat_manager)
2351 g_signal_handler_disconnect (self->priv->chat_manager,
2352 self->priv->chat_manager_chats_changed_id);
2353 g_object_unref (self->priv->chat_manager);
2354 self->priv->chat_manager = NULL;
2357 chat_windows = g_list_remove (chat_windows, self);
2359 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2363 chat_window_get_property (GObject *object,
2368 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2370 switch (property_id)
2372 case PROP_INDIVIDUAL_MGR:
2373 g_value_set_object (value, self->priv->individual_mgr);
2375 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2381 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2383 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2386 object_class->get_property = chat_window_get_property;
2387 object_class->finalize = chat_window_finalize;
2389 spec = g_param_spec_object ("individual-manager", "individual-manager",
2390 "EmpathyIndividualManager",
2391 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2392 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2393 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2395 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2399 empathy_chat_window_init (EmpathyChatWindow *self)
2402 GtkAccelGroup *accel_group;
2407 GtkWidget *chat_vbox;
2409 EmpathySmileyManager *smiley_manager;
2411 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2412 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2414 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2415 gui = tpaw_builder_get_file (filename,
2416 "chat_vbox", &chat_vbox,
2417 "ui_manager", &self->priv->ui_manager,
2418 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2419 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2420 "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2421 "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2422 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2423 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2424 "menu_edit_cut", &self->priv->menu_edit_cut,
2425 "menu_edit_copy", &self->priv->menu_edit_copy,
2426 "menu_edit_paste", &self->priv->menu_edit_paste,
2427 "menu_edit_find", &self->priv->menu_edit_find,
2428 "menu_tabs_next", &self->priv->menu_tabs_next,
2429 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2430 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2431 "menu_tabs_left", &self->priv->menu_tabs_left,
2432 "menu_tabs_right", &self->priv->menu_tabs_right,
2433 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2437 tpaw_builder_connect (gui, self,
2438 "menu_conv", "activate", chat_window_conv_activate_cb,
2439 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2440 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2441 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2442 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2443 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2444 "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2445 "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2446 "menu_conv_close", "activate", chat_window_close_activate_cb,
2447 "menu_edit", "activate", chat_window_edit_activate_cb,
2448 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2449 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2450 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2451 "menu_edit_find", "activate", chat_window_find_activate_cb,
2452 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2453 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2454 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2455 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2456 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2457 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2458 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2459 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2462 empathy_set_css_provider (GTK_WIDGET (self));
2464 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2465 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2466 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2467 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2469 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2471 self->priv->notebook = gtk_notebook_new ();
2473 g_signal_connect (self->priv->notebook, "create-window",
2474 G_CALLBACK (notebook_create_window_cb), self);
2476 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2478 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2479 "EmpathyChatWindow");
2480 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2481 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2482 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2483 gtk_widget_show (self->priv->notebook);
2486 accel_group = gtk_accel_group_new ();
2487 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2489 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2491 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2494 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2498 g_object_unref (accel_group);
2500 /* Set up drag target lists */
2501 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2502 G_N_ELEMENTS (drag_types_dest_contact));
2504 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2505 G_N_ELEMENTS (drag_types_dest_file));
2507 /* Set up smiley menu */
2508 smiley_manager = empathy_smiley_manager_dup_singleton ();
2509 submenu = empathy_smiley_menu_new (smiley_manager,
2510 chat_window_insert_smiley_activate_cb, self);
2512 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2513 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2514 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2515 g_object_unref (smiley_manager);
2517 /* Set up signals we can't do with ui file since we may need to
2518 * block/unblock them at some later stage.
2521 g_signal_connect (self, "delete_event",
2522 G_CALLBACK (chat_window_delete_event_cb), self);
2523 g_signal_connect (self, "focus_in_event",
2524 G_CALLBACK (chat_window_focus_in_event_cb), self);
2525 g_signal_connect (self, "focus_out_event",
2526 G_CALLBACK (chat_window_focus_out_event_cb), self);
2527 g_signal_connect_after (self->priv->notebook, "switch_page",
2528 G_CALLBACK (chat_window_page_switched_cb), self);
2529 g_signal_connect (self->priv->notebook, "page_added",
2530 G_CALLBACK (chat_window_page_added_cb), self);
2531 g_signal_connect (self->priv->notebook, "page_removed",
2532 G_CALLBACK (chat_window_page_removed_cb), self);
2534 /* Set up drag and drop */
2535 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2536 GTK_DEST_DEFAULT_HIGHLIGHT,
2538 G_N_ELEMENTS (drag_types_dest),
2539 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2541 /* connect_after to allow GtkNotebook's built-in tab switching */
2542 g_signal_connect_after (self->priv->notebook, "drag-motion",
2543 G_CALLBACK (chat_window_drag_motion), self);
2544 g_signal_connect (self->priv->notebook, "drag-data-received",
2545 G_CALLBACK (chat_window_drag_data_received), self);
2546 g_signal_connect (self->priv->notebook, "drag-drop",
2547 G_CALLBACK (chat_window_drag_drop), self);
2549 chat_windows = g_list_prepend (chat_windows, self);
2551 /* Set up private details */
2552 self->priv->chats = NULL;
2553 self->priv->current_chat = NULL;
2554 self->priv->notification = NULL;
2556 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2558 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2559 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2560 self->priv->chat_manager, "closed-chats-changed",
2561 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2563 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2564 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2566 g_object_ref (self->priv->ui_manager);
2567 g_object_unref (gui);
2570 /* Returns the window to open a new tab in if there is a suitable window,
2571 * otherwise, returns NULL indicating that a new window should be added.
2573 static EmpathyChatWindow *
2574 empathy_chat_window_get_default (gboolean room)
2576 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2578 gboolean separate_windows = TRUE;
2580 separate_windows = g_settings_get_boolean (gsettings,
2581 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2583 g_object_unref (gsettings);
2585 if (separate_windows)
2586 /* Always create a new window */
2589 for (l = chat_windows; l; l = l->next)
2591 EmpathyChatWindow *chat_window;
2592 guint nb_rooms, nb_private;
2594 chat_window = l->data;
2596 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2598 /* Skip the window if there aren't any rooms in it */
2599 if (room && nb_rooms == 0)
2602 /* Skip the window if there aren't any 1-1 chats in it */
2603 if (!room && nb_private == 0)
2613 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2617 GtkWidget *popup_label;
2619 GValue value = { 0, };
2621 g_return_if_fail (self != NULL);
2622 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2624 /* Reference the chat object */
2625 g_object_ref (chat);
2627 /* If this window has just been created, position it */
2628 if (self->priv->chats == NULL)
2630 const gchar *name = "chat-window";
2631 gboolean separate_windows;
2633 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2634 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2636 if (empathy_chat_is_room (chat))
2637 name = "room-window";
2639 if (separate_windows)
2643 /* Save current position of the window */
2644 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2646 /* First bind to the 'generic' name. So new window for which we didn't
2647 * save a geometry yet will have the geometry of the last saved
2648 * window (bgo #601191). */
2649 empathy_geometry_bind (GTK_WINDOW (self), name);
2651 /* Restore previous position of the window so the newly created window
2652 * won't be in the same position as the latest saved window and so
2653 * completely hide it. */
2654 gtk_window_move (GTK_WINDOW (self), x, y);
2656 /* Then bind it to the name of the contact/room so we'll save the
2657 * geometry specific to this window */
2658 name = empathy_chat_get_id (chat);
2661 empathy_geometry_bind (GTK_WINDOW (self), name);
2664 child = GTK_WIDGET (chat);
2665 label = chat_window_create_label (self, chat, TRUE);
2666 popup_label = chat_window_create_label (self, chat, FALSE);
2667 gtk_widget_show (child);
2669 g_signal_connect (chat, "notify::name",
2670 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2671 g_signal_connect (chat, "notify::subject",
2672 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2673 g_signal_connect (chat, "notify::remote-contact",
2674 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2675 g_signal_connect (chat, "notify::sms-channel",
2676 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2677 g_signal_connect (chat, "notify::n-messages-sending",
2678 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2679 g_signal_connect (chat, "notify::nb-unread-messages",
2680 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2681 chat_window_chat_notify_cb (chat);
2683 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2685 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2686 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2687 g_value_init (&value, G_TYPE_BOOLEAN);
2688 g_value_set_boolean (&value, TRUE);
2689 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2690 child, "tab-expand" , &value);
2691 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2692 child, "tab-fill" , &value);
2693 g_value_unset (&value);
2695 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2699 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2703 EmpathyContact *remote_contact;
2704 EmpathyChatManager *chat_manager;
2706 g_return_if_fail (self != NULL);
2707 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2709 g_signal_handlers_disconnect_by_func (chat,
2710 chat_window_chat_notify_cb, NULL);
2712 remote_contact = g_object_get_data (G_OBJECT (chat),
2713 "chat-window-remote-contact");
2717 g_signal_handlers_disconnect_by_func (remote_contact,
2718 chat_window_update_chat_tab, chat);
2721 chat_manager = empathy_chat_manager_dup_singleton ();
2722 empathy_chat_manager_closed_chat (chat_manager, chat);
2723 g_object_unref (chat_manager);
2725 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2727 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2729 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2731 g_object_unref (chat);
2735 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2736 EmpathyChatWindow *new_window,
2741 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2742 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2743 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2745 widget = GTK_WIDGET (chat);
2747 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2748 G_OBJECT (widget)->ref_count);
2750 /* We reference here to make sure we don't loose the widget
2751 * and the EmpathyChat object during the move.
2753 g_object_ref (chat);
2754 g_object_ref (widget);
2756 empathy_chat_window_remove_chat (old_window, chat);
2757 empathy_chat_window_add_chat (new_window, chat);
2759 g_object_unref (widget);
2760 g_object_unref (chat);
2764 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2769 g_return_if_fail (self != NULL);
2770 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2772 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2775 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2780 empathy_chat_window_find_chat (TpAccount *account,
2782 gboolean sms_channel)
2786 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2788 for (l = chat_windows; l; l = l->next)
2790 EmpathyChatWindow *window = l->data;
2793 for (ll = window->priv->chats; ll; ll = ll->next)
2799 if (account == empathy_chat_get_account (chat) &&
2800 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2801 sms_channel == empathy_chat_is_sms_channel (chat))
2810 empathy_chat_window_present_chat (EmpathyChat *chat,
2813 EmpathyChatWindow *self;
2814 guint32 x_timestamp;
2816 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2818 self = chat_window_find_chat (chat);
2820 /* If the chat has no window, create one */
2823 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2826 self = empathy_chat_window_new ();
2828 /* we want to display the newly created window even if we
2829 * don't present it */
2830 gtk_widget_show (GTK_WIDGET (self));
2833 empathy_chat_window_add_chat (self, chat);
2836 /* Don't force the window to show itself when it wasn't
2837 * an action by the user
2839 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2842 if (x_timestamp != GDK_CURRENT_TIME)
2844 /* Don't present or switch tab if the action was earlier than the
2845 * last actions X time, accounting for overflow and the first ever
2848 if (self->priv->x_user_action_time != 0
2849 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2852 self->priv->x_user_action_time = x_timestamp;
2855 empathy_chat_window_switch_to_chat (self, chat);
2857 /* Don't use empathy_window_present_with_time () which would move the window
2858 * to our current desktop but move to the window's desktop instead. This is
2859 * more coherent with Shell's 'app is ready' notication which moves the view
2860 * to the app desktop rather than moving the app itself. */
2861 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2863 gtk_widget_grab_focus (chat->input_text_view);
2868 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2873 guint _nb_rooms = 0, _nb_private = 0;
2875 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2877 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2883 if (nb_rooms != NULL)
2884 *nb_rooms = _nb_rooms;
2885 if (nb_private != NULL)
2886 *nb_private = _nb_private;
2889 EmpathyIndividualManager *
2890 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2892 return self->priv->individual_mgr;