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-tp-contact-factory.h>
48 #include <libempathy/empathy-contact-list.h>
49 #include <libempathy/empathy-request-util.h>
50 #include <libempathy/empathy-individual-manager.h>
52 #include <libempathy-gtk/empathy-images.h>
53 #include <libempathy-gtk/empathy-contact-dialogs.h>
54 #include <libempathy-gtk/empathy-log-window.h>
55 #include <libempathy-gtk/empathy-geometry.h>
56 #include <libempathy-gtk/empathy-smiley-manager.h>
57 #include <libempathy-gtk/empathy-sound-manager.h>
58 #include <libempathy-gtk/empathy-ui-utils.h>
59 #include <libempathy-gtk/empathy-notify-manager.h>
61 #include "empathy-chat-manager.h"
62 #include "empathy-chat-window.h"
63 #include "empathy-about-dialog.h"
64 #include "empathy-invite-participant-dialog.h"
65 #include "gedit-close-button.h"
67 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
68 #include <libempathy/empathy-debug.h>
70 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
72 #define X_EARLIER_OR_EQL(t1, t2) \
73 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
74 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
77 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
79 EmpathyChat *current_chat;
82 gboolean dnd_same_window;
83 EmpathyChatroomManager *chatroom_manager;
84 EmpathyNotifyManager *notify_mgr;
87 NotifyNotification *notification;
89 GtkTargetList *contact_targets;
90 GtkTargetList *file_targets;
92 EmpathyChatManager *chat_manager;
93 gulong chat_manager_chats_changed_id;
96 GtkUIManager *ui_manager;
97 GtkAction *menu_conv_insert_smiley;
98 GtkAction *menu_conv_favorite;
99 GtkAction *menu_conv_always_urgent;
100 GtkAction *menu_conv_toggle_contacts;
102 GtkAction *menu_edit_cut;
103 GtkAction *menu_edit_copy;
104 GtkAction *menu_edit_paste;
105 GtkAction *menu_edit_find;
107 GtkAction *menu_tabs_next;
108 GtkAction *menu_tabs_prev;
109 GtkAction *menu_tabs_undo_close_tab;
110 GtkAction *menu_tabs_left;
111 GtkAction *menu_tabs_right;
112 GtkAction *menu_tabs_detach;
114 /* Last user action time we acted upon to show a tab */
115 guint32 x_user_action_time;
117 GSettings *gsettings_chat;
118 GSettings *gsettings_notif;
119 GSettings *gsettings_ui;
121 EmpathySoundManager *sound_mgr;
122 } EmpathyChatWindowPriv;
124 static GList *chat_windows = NULL;
126 static const guint tab_accel_keys[] = {
127 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
128 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
132 DND_DRAG_TYPE_CONTACT_ID,
133 DND_DRAG_TYPE_INDIVIDUAL_ID,
134 DND_DRAG_TYPE_URI_LIST,
138 static const GtkTargetEntry drag_types_dest[] = {
139 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
140 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
141 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
142 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
143 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
146 static const GtkTargetEntry drag_types_dest_contact[] = {
147 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
148 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
151 static const GtkTargetEntry drag_types_dest_file[] = {
152 /* must be first to be prioritized, in order to receive the
153 * note's file path from Tomboy instead of an URI */
154 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
155 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
158 static void chat_window_update (EmpathyChatWindow *window,
159 gboolean update_contact_menu);
161 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
164 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
167 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
168 EmpathyChatWindow *new_window,
171 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
175 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
178 chat_window_accel_cb (GtkAccelGroup *accelgroup,
182 EmpathyChatWindow *window)
184 EmpathyChatWindowPriv *priv;
188 priv = GET_PRIV (window);
190 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
191 if (tab_accel_keys[i] == key) {
198 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
202 static EmpathyChatWindow *
203 chat_window_find_chat (EmpathyChat *chat)
205 EmpathyChatWindowPriv *priv;
208 for (l = chat_windows; l; l = l->next) {
209 priv = GET_PRIV (l->data);
210 ll = g_list_find (priv->chats, chat);
220 remove_all_chats (EmpathyChatWindow *window)
222 EmpathyChatWindowPriv *priv;
224 priv = GET_PRIV (window);
225 g_object_ref (window);
227 while (priv->chats) {
228 empathy_chat_window_remove_chat (window, priv->chats->data);
231 g_object_unref (window);
235 confirm_close_response_cb (GtkWidget *dialog,
237 EmpathyChatWindow *window)
241 chat = g_object_get_data (G_OBJECT (dialog), "chat");
243 gtk_widget_destroy (dialog);
245 if (response != GTK_RESPONSE_ACCEPT)
249 empathy_chat_window_remove_chat (window, chat);
251 remove_all_chats (window);
256 confirm_close (EmpathyChatWindow *window,
257 gboolean close_window,
261 EmpathyChatWindowPriv *priv;
263 gchar *primary, *secondary;
265 g_return_if_fail (n_rooms > 0);
268 g_return_if_fail (chat == NULL);
270 g_return_if_fail (chat != NULL);
273 priv = GET_PRIV (window);
275 /* If there are no chats in this window, how could we possibly have got
278 g_return_if_fail (priv->chats != NULL);
280 /* Treat closing a window which only has one tab exactly like closing
283 if (close_window && priv->chats->next == NULL) {
284 close_window = FALSE;
285 chat = priv->chats->data;
289 primary = g_strdup (_("Close this window?"));
292 gchar *chat_name = empathy_chat_dup_name (chat);
293 secondary = g_strdup_printf (
294 _("Closing this window will leave %s. You will "
295 "not receive any further messages until you "
300 secondary = g_strdup_printf (
301 /* Note to translators: the number of chats will
302 * always be at least 2.
305 "Closing this window will leave a chat room. You will "
306 "not receive any further messages until you rejoin it.",
307 "Closing this window will leave %u chat rooms. You will "
308 "not receive any further messages until you rejoin them.",
313 gchar *chat_name = empathy_chat_dup_name (chat);
314 primary = g_strdup_printf (_("Leave %s?"), chat_name);
315 secondary = g_strdup (_("You will not receive any further messages from this chat "
316 "room until you rejoin it."));
320 dialog = gtk_message_dialog_new (
321 GTK_WINDOW (priv->dialog),
322 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
327 gtk_window_set_title (GTK_WINDOW (dialog), "");
328 g_object_set (dialog, "secondary-text", secondary, NULL);
333 gtk_dialog_add_button (GTK_DIALOG (dialog),
334 close_window ? _("Close window") : _("Leave room"),
335 GTK_RESPONSE_ACCEPT);
336 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
337 GTK_RESPONSE_ACCEPT);
340 g_object_set_data (G_OBJECT (dialog), "chat", chat);
343 g_signal_connect (dialog, "response",
344 G_CALLBACK (confirm_close_response_cb), window);
346 gtk_window_present (GTK_WINDOW (dialog));
349 /* Returns TRUE if we should check if the user really wants to leave. If it's
350 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
351 * the user is actually in the room as opposed to having been kicked or gone
352 * offline or something), then we should check.
355 chat_needs_close_confirmation (EmpathyChat *chat)
357 return (empathy_chat_is_room (chat)
358 && empathy_chat_get_tp_chat (chat) != NULL);
362 maybe_close_chat (EmpathyChatWindow *window,
365 g_return_if_fail (chat != NULL);
367 if (chat_needs_close_confirmation (chat)) {
368 confirm_close (window, FALSE, 1, chat);
370 empathy_chat_window_remove_chat (window, chat);
375 chat_window_close_clicked_cb (GtkAction *action,
378 EmpathyChatWindow *window;
380 window = chat_window_find_chat (chat);
381 maybe_close_chat (window, chat);
385 chat_tab_style_updated_cb (GtkWidget *hbox,
389 int char_width, h, w;
390 PangoContext *context;
391 const PangoFontDescription *font_desc;
392 PangoFontMetrics *metrics;
394 button = g_object_get_data (G_OBJECT (user_data),
395 "chat-window-tab-close-button");
396 context = gtk_widget_get_pango_context (hbox);
398 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
399 GTK_STATE_FLAG_NORMAL);
401 metrics = pango_context_get_metrics (context, font_desc,
402 pango_context_get_language (context));
403 char_width = pango_font_metrics_get_approximate_char_width (metrics);
404 pango_font_metrics_unref (metrics);
406 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
407 GTK_ICON_SIZE_MENU, &w, &h);
409 /* Request at least about 12 chars width plus at least space for the status
410 * image and the close button */
411 gtk_widget_set_size_request (hbox,
412 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
414 gtk_widget_set_size_request (button, w, h);
418 chat_window_create_label (EmpathyChatWindow *window,
420 gboolean is_tab_label)
423 GtkWidget *name_label;
424 GtkWidget *status_image;
425 GtkWidget *event_box;
426 GtkWidget *event_box_hbox;
427 PangoAttrList *attr_list;
428 PangoAttribute *attr;
430 /* The spacing between the button and the label. */
431 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
433 event_box = gtk_event_box_new ();
434 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
436 name_label = gtk_label_new (NULL);
438 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
440 attr_list = pango_attr_list_new ();
441 attr = pango_attr_scale_new (1/1.2);
442 attr->start_index = 0;
443 attr->end_index = -1;
444 pango_attr_list_insert (attr_list, attr);
445 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
446 pango_attr_list_unref (attr_list);
448 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
449 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
450 g_object_set_data (G_OBJECT (chat),
451 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
454 status_image = gtk_image_new ();
456 /* Spacing between the icon and label. */
457 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
459 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
460 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
462 g_object_set_data (G_OBJECT (chat),
463 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
465 g_object_set_data (G_OBJECT (chat),
466 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
469 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
470 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
473 GtkWidget *close_button;
474 GtkWidget *sending_spinner;
476 sending_spinner = gtk_spinner_new ();
478 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
480 g_object_set_data (G_OBJECT (chat),
481 "chat-window-tab-sending-spinner",
484 close_button = gedit_close_button_new ();
485 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
487 /* We don't want focus/keynav for the button to avoid clutter, and
488 * Ctrl-W works anyway.
490 gtk_widget_set_can_focus (close_button, FALSE);
491 gtk_widget_set_can_default (close_button, FALSE);
493 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
495 g_signal_connect (close_button,
497 G_CALLBACK (chat_window_close_clicked_cb),
500 /* React to theme changes and also setup the size correctly. */
501 g_signal_connect (hbox,
503 G_CALLBACK (chat_tab_style_updated_cb),
507 gtk_widget_show_all (hbox);
513 _submenu_notify_visible_changed_cb (GObject *object,
517 g_signal_handlers_disconnect_by_func (object,
518 _submenu_notify_visible_changed_cb,
520 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
524 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
529 gboolean wrap_around;
530 gboolean is_connected;
533 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
534 first_page = (page_num == 0);
535 last_page = (page_num == (num_pages - 1));
536 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
538 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
540 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
542 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
544 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
545 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
546 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
547 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
551 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
552 EmpathyChatWindow *self)
554 EmpathyTpChat *tp_chat;
555 TpConnection *connection;
557 gboolean sensitive = FALSE;
559 g_return_if_fail (priv->current_chat != NULL);
561 action = gtk_ui_manager_get_action (priv->ui_manager,
562 "/chats_menubar/menu_conv/menu_conv_invite_participant");
563 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
565 if (tp_chat != NULL) {
566 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
568 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
569 (tp_connection_get_status (connection, NULL) ==
570 TP_CONNECTION_STATUS_CONNECTED);
573 gtk_action_set_sensitive (action, sensitive);
577 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
578 EmpathyChatWindow *window)
580 GtkWidget *menu, *submenu, *orig_submenu;
582 menu = gtk_ui_manager_get_widget (priv->ui_manager,
583 "/chats_menubar/menu_contact");
584 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
586 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
587 submenu = empathy_chat_get_contact_menu (priv->current_chat);
589 if (submenu != NULL) {
590 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
591 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
593 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
594 gtk_widget_show (menu);
595 gtk_widget_set_sensitive (menu, TRUE);
597 gtk_widget_set_sensitive (menu, FALSE);
600 tp_g_signal_connect_object (orig_submenu,
602 (GCallback)_submenu_notify_visible_changed_cb,
608 get_all_unread_messages (EmpathyChatWindowPriv *priv)
613 for (l = priv->chats; l != NULL; l = g_list_next (l))
614 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
620 get_window_title_name (EmpathyChatWindowPriv *priv)
622 gchar *active_name, *ret;
624 guint current_unread_msgs;
626 nb_chats = g_list_length (priv->chats);
627 g_assert (nb_chats > 0);
629 active_name = empathy_chat_dup_name (priv->current_chat);
631 current_unread_msgs = empathy_chat_get_nb_unread_messages (
636 if (current_unread_msgs == 0)
637 ret = g_strdup (active_name);
639 ret = g_strdup_printf (ngettext (
641 "%s (%d unread)", current_unread_msgs),
642 active_name, current_unread_msgs);
644 guint nb_others = nb_chats - 1;
645 guint all_unread_msgs;
647 all_unread_msgs = get_all_unread_messages (priv);
649 if (all_unread_msgs == 0) {
650 /* no unread message */
651 ret = g_strdup_printf (ngettext (
653 "%s (and %u others)", nb_others),
654 active_name, nb_others);
657 else if (all_unread_msgs == current_unread_msgs) {
658 /* unread messages are in the current tab */
659 ret = g_strdup_printf (ngettext (
661 "%s (%d unread)", current_unread_msgs),
662 active_name, current_unread_msgs);
665 else if (current_unread_msgs == 0) {
666 /* unread messages are in other tabs */
667 ret = g_strdup_printf (ngettext (
668 "%s (%d unread from others)",
669 "%s (%d unread from others)",
671 active_name, all_unread_msgs);
675 /* unread messages are in all the tabs */
676 ret = g_strdup_printf (ngettext (
677 "%s (%d unread from all)",
678 "%s (%d unread from all)",
680 active_name, all_unread_msgs);
684 g_free (active_name);
690 chat_window_title_update (EmpathyChatWindowPriv *priv)
694 name = get_window_title_name (priv);
695 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
700 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
703 EmpathyContact *remote_contact;
704 gboolean avatar_in_icon;
707 n_chats = g_list_length (priv->chats);
709 /* Update window icon */
711 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
712 EMPATHY_IMAGE_MESSAGE);
714 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
715 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
717 if (n_chats == 1 && avatar_in_icon) {
718 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
719 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
720 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
723 g_object_unref (icon);
726 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
732 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
736 GtkWidget *chat_close_button;
739 if (num_pages == 1) {
740 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
741 chat_close_button = g_object_get_data (G_OBJECT (chat),
742 "chat-window-tab-close-button");
743 gtk_widget_hide (chat_close_button);
745 for (i=0; i<num_pages; i++) {
746 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
747 chat_close_button = g_object_get_data (G_OBJECT (chat),
748 "chat-window-tab-close-button");
749 gtk_widget_show (chat_close_button);
755 chat_window_update (EmpathyChatWindow *window,
756 gboolean update_contact_menu)
758 EmpathyChatWindowPriv *priv = GET_PRIV (window);
761 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
763 /* Update Tab menu */
764 chat_window_menu_context_update (priv,
767 chat_window_conversation_menu_update (priv, window);
769 /* If this update is due to a focus-in event, we know the menu will be
770 the same as when we last left it, so no work to do. Besides, if we
771 swap out the menu on a focus-in, we may confuse any external global
773 if (update_contact_menu) {
774 chat_window_contact_menu_update (priv,
778 chat_window_title_update (priv);
780 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
782 chat_window_close_button_update (priv,
787 append_markup_printf (GString *string,
794 va_start (args, format);
796 tmp = g_markup_vprintf_escaped (format, args);
797 g_string_append (string, tmp);
804 chat_window_update_chat_tab_full (EmpathyChat *chat,
805 gboolean update_contact_menu)
807 EmpathyChatWindow *window;
808 EmpathyChatWindowPriv *priv;
809 EmpathyContact *remote_contact;
813 const gchar *subject;
814 const gchar *status = NULL;
818 const gchar *icon_name;
819 GtkWidget *tab_image;
820 GtkWidget *menu_image;
821 GtkWidget *sending_spinner;
824 window = chat_window_find_chat (chat);
828 priv = GET_PRIV (window);
830 /* Get information */
831 name = empathy_chat_dup_name (chat);
832 account = empathy_chat_get_account (chat);
833 subject = empathy_chat_get_subject (chat);
834 remote_contact = empathy_chat_get_remote_contact (chat);
836 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
837 name, tp_proxy_get_object_path (account), subject, remote_contact);
839 /* Update tab image */
840 if (empathy_chat_get_tp_chat (chat) == NULL) {
841 /* No TpChat, we are disconnected */
844 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
845 icon_name = EMPATHY_IMAGE_MESSAGE;
847 else if (remote_contact && empathy_chat_is_composing (chat)) {
848 icon_name = EMPATHY_IMAGE_TYPING;
850 else if (empathy_chat_is_sms_channel (chat)) {
851 icon_name = EMPATHY_IMAGE_SMS;
853 else if (remote_contact) {
854 icon_name = empathy_icon_name_for_contact (remote_contact);
856 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
859 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
860 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
861 if (icon_name != NULL) {
862 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
863 gtk_widget_show (tab_image);
864 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
865 gtk_widget_show (menu_image);
867 gtk_widget_hide (tab_image);
868 gtk_widget_hide (menu_image);
871 /* Update the sending spinner */
872 nb_sending = empathy_chat_get_n_messages_sending (chat);
873 sending_spinner = g_object_get_data (G_OBJECT (chat),
874 "chat-window-tab-sending-spinner");
876 g_object_set (sending_spinner,
877 "active", nb_sending > 0,
878 "visible", nb_sending > 0,
881 /* Update tab tooltip */
882 tooltip = g_string_new (NULL);
884 if (remote_contact) {
885 id = empathy_contact_get_id (remote_contact);
886 status = empathy_contact_get_presence_message (remote_contact);
891 if (empathy_chat_is_sms_channel (chat)) {
892 append_markup_printf (tooltip, "%s ", _("SMS:"));
895 append_markup_printf (tooltip,
896 "<b>%s</b><small> (%s)</small>",
898 tp_account_get_display_name (account));
900 if (nb_sending > 0) {
901 char *tmp = g_strdup_printf (
902 ngettext ("Sending %d message",
903 "Sending %d messages",
907 g_string_append (tooltip, "\n");
908 g_string_append (tooltip, tmp);
910 gtk_widget_set_tooltip_text (sending_spinner, tmp);
914 if (!EMP_STR_EMPTY (status)) {
915 append_markup_printf (tooltip, "\n<i>%s</i>", status);
919 append_markup_printf (tooltip, "\n<b>%s</b> %s",
920 _("Topic:"), subject);
923 if (remote_contact && empathy_chat_is_composing (chat)) {
924 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
927 if (remote_contact != NULL) {
928 const gchar * const *types;
930 types = empathy_contact_get_client_types (remote_contact);
931 if (types != NULL && !tp_strdiff (types[0], "phone")) {
932 /* I'm on a phone ! */
935 name = g_strdup_printf ("☎ %s", name);
940 markup = g_string_free (tooltip, FALSE);
941 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
942 gtk_widget_set_tooltip_markup (widget, markup);
943 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
944 gtk_widget_set_tooltip_markup (widget, markup);
947 /* Update tab and menu label */
948 if (empathy_chat_is_highlighted (chat)) {
949 markup = g_markup_printf_escaped (
950 "<span color=\"red\" weight=\"bold\">%s</span>",
953 markup = g_markup_escape_text (name, -1);
956 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
957 gtk_label_set_markup (GTK_LABEL (widget), markup);
958 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
959 gtk_label_set_markup (GTK_LABEL (widget), markup);
962 /* Update the window if it's the current chat */
963 if (priv->current_chat == chat) {
964 chat_window_update (window, update_contact_menu);
971 chat_window_update_chat_tab (EmpathyChat *chat)
973 chat_window_update_chat_tab_full (chat, TRUE);
977 chat_window_chat_notify_cb (EmpathyChat *chat)
979 EmpathyChatWindow *window;
980 EmpathyContact *old_remote_contact;
981 EmpathyContact *remote_contact = NULL;
983 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
984 remote_contact = empathy_chat_get_remote_contact (chat);
986 if (old_remote_contact != remote_contact) {
987 /* The remote-contact associated with the chat changed, we need
988 * to keep track of any change of that contact and update the
989 * window each time. */
990 if (remote_contact) {
991 g_signal_connect_swapped (remote_contact, "notify",
992 G_CALLBACK (chat_window_update_chat_tab),
995 if (old_remote_contact) {
996 g_signal_handlers_disconnect_by_func (old_remote_contact,
997 chat_window_update_chat_tab,
1001 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1002 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1005 chat_window_update_chat_tab (chat);
1007 window = chat_window_find_chat (chat);
1008 if (window != NULL) {
1009 chat_window_update (window, FALSE);
1014 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1015 EmpathySmiley *smiley,
1018 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1020 GtkTextBuffer *buffer;
1023 chat = priv->current_chat;
1025 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1026 gtk_text_buffer_get_end_iter (buffer, &iter);
1027 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1031 chat_window_conv_activate_cb (GtkAction *action,
1032 EmpathyChatWindow *window)
1034 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1037 EmpathyContact *remote_contact = NULL;
1039 /* Favorite room menu */
1040 is_room = empathy_chat_is_room (priv->current_chat);
1044 gboolean found = FALSE;
1045 EmpathyChatroom *chatroom;
1047 room = empathy_chat_get_id (priv->current_chat);
1048 account = empathy_chat_get_account (priv->current_chat);
1049 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1051 if (chatroom != NULL)
1052 found = empathy_chatroom_is_favorite (chatroom);
1054 DEBUG ("This room %s favorite", found ? "is" : "is not");
1055 gtk_toggle_action_set_active (
1056 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1058 if (chatroom != NULL)
1059 found = empathy_chatroom_is_always_urgent (chatroom);
1061 gtk_toggle_action_set_active (
1062 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1065 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1066 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1068 /* Show contacts menu */
1069 g_object_get (priv->current_chat,
1070 "remote-contact", &remote_contact,
1071 "show-contacts", &active,
1073 if (remote_contact == NULL) {
1074 gtk_toggle_action_set_active (
1075 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1078 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1079 (remote_contact == NULL));
1080 if (remote_contact != NULL) {
1081 g_object_unref (remote_contact);
1086 chat_window_clear_activate_cb (GtkAction *action,
1087 EmpathyChatWindow *window)
1089 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1091 empathy_chat_clear (priv->current_chat);
1095 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1096 EmpathyChatWindow *window)
1098 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1103 EmpathyChatroom *chatroom;
1105 active = gtk_toggle_action_get_active (toggle_action);
1106 account = empathy_chat_get_account (priv->current_chat);
1107 room = empathy_chat_get_id (priv->current_chat);
1108 name = empathy_chat_dup_name (priv->current_chat);
1110 chatroom = empathy_chatroom_manager_ensure_chatroom (
1111 priv->chatroom_manager,
1116 empathy_chatroom_set_favorite (chatroom, active);
1117 g_object_unref (chatroom);
1122 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1123 EmpathyChatWindow *window)
1125 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1130 EmpathyChatroom *chatroom;
1132 active = gtk_toggle_action_get_active (toggle_action);
1133 account = empathy_chat_get_account (priv->current_chat);
1134 room = empathy_chat_get_id (priv->current_chat);
1135 name = empathy_chat_dup_name (priv->current_chat);
1137 chatroom = empathy_chatroom_manager_ensure_chatroom (
1138 priv->chatroom_manager,
1143 empathy_chatroom_set_always_urgent (chatroom, active);
1144 g_object_unref (chatroom);
1149 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1150 EmpathyChatWindow *window)
1152 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1155 active = gtk_toggle_action_get_active (toggle_action);
1157 empathy_chat_set_show_contacts (priv->current_chat, active);
1161 chat_window_invite_participant_activate_cb (GtkAction *action,
1162 EmpathyChatWindow *window)
1164 EmpathyChatWindowPriv *priv;
1166 EmpathyTpChat *tp_chat;
1169 priv = GET_PRIV (window);
1171 g_return_if_fail (priv->current_chat != NULL);
1173 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1175 dialog = empathy_invite_participant_dialog_new (
1176 GTK_WINDOW (priv->dialog), tp_chat);
1177 gtk_widget_show (dialog);
1179 response = gtk_dialog_run (GTK_DIALOG (dialog));
1181 if (response == GTK_RESPONSE_ACCEPT) {
1182 TpContact *tp_contact;
1183 EmpathyContact *contact;
1185 tp_contact = empathy_invite_participant_dialog_get_selected (
1186 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1187 if (tp_contact == NULL) goto out;
1189 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1191 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1193 g_object_unref (contact);
1197 gtk_widget_destroy (dialog);
1201 chat_window_close_activate_cb (GtkAction *action,
1202 EmpathyChatWindow *window)
1204 EmpathyChatWindowPriv *priv;
1206 priv = GET_PRIV (window);
1208 g_return_if_fail (priv->current_chat != NULL);
1210 maybe_close_chat (window, priv->current_chat);
1214 chat_window_edit_activate_cb (GtkAction *action,
1215 EmpathyChatWindow *window)
1217 EmpathyChatWindowPriv *priv;
1218 GtkClipboard *clipboard;
1219 GtkTextBuffer *buffer;
1220 gboolean text_available;
1222 priv = GET_PRIV (window);
1224 g_return_if_fail (priv->current_chat != NULL);
1226 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1227 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1228 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1229 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1233 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1234 if (gtk_text_buffer_get_has_selection (buffer)) {
1235 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1236 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1240 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1242 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1243 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1246 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1247 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1248 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1252 chat_window_cut_activate_cb (GtkAction *action,
1253 EmpathyChatWindow *window)
1255 EmpathyChatWindowPriv *priv;
1257 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1259 priv = GET_PRIV (window);
1261 empathy_chat_cut (priv->current_chat);
1265 chat_window_copy_activate_cb (GtkAction *action,
1266 EmpathyChatWindow *window)
1268 EmpathyChatWindowPriv *priv;
1270 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1272 priv = GET_PRIV (window);
1274 empathy_chat_copy (priv->current_chat);
1278 chat_window_paste_activate_cb (GtkAction *action,
1279 EmpathyChatWindow *window)
1281 EmpathyChatWindowPriv *priv;
1283 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1285 priv = GET_PRIV (window);
1287 empathy_chat_paste (priv->current_chat);
1291 chat_window_find_activate_cb (GtkAction *action,
1292 EmpathyChatWindow *window)
1294 EmpathyChatWindowPriv *priv;
1296 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1298 priv = GET_PRIV (window);
1300 empathy_chat_find (priv->current_chat);
1304 chat_window_tabs_next_activate_cb (GtkAction *action,
1305 EmpathyChatWindow *window)
1307 EmpathyChatWindowPriv *priv;
1308 gint index_, numPages;
1309 gboolean wrap_around;
1311 priv = GET_PRIV (window);
1313 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1314 &wrap_around, NULL);
1316 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1317 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1319 if (index_ == (numPages - 1) && wrap_around) {
1320 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1324 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1328 chat_window_tabs_previous_activate_cb (GtkAction *action,
1329 EmpathyChatWindow *window)
1331 EmpathyChatWindowPriv *priv;
1332 gint index_, numPages;
1333 gboolean wrap_around;
1335 priv = GET_PRIV (window);
1337 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1338 &wrap_around, NULL);
1340 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1341 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1343 if (index_ <= 0 && wrap_around) {
1344 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1348 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1352 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1353 EmpathyChatWindow *window)
1355 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1356 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1357 empathy_get_current_action_time ());
1361 chat_window_tabs_left_activate_cb (GtkAction *action,
1362 EmpathyChatWindow *window)
1364 EmpathyChatWindowPriv *priv;
1366 gint index_, num_pages;
1368 priv = GET_PRIV (window);
1370 chat = priv->current_chat;
1371 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1376 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1380 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1381 chat_window_menu_context_update (priv, num_pages);
1385 chat_window_tabs_right_activate_cb (GtkAction *action,
1386 EmpathyChatWindow *window)
1388 EmpathyChatWindowPriv *priv;
1390 gint index_, num_pages;
1392 priv = GET_PRIV (window);
1394 chat = priv->current_chat;
1395 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);
1405 static EmpathyChatWindow *
1406 empathy_chat_window_new (void)
1408 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1412 chat_window_detach_activate_cb (GtkAction *action,
1413 EmpathyChatWindow *window)
1415 EmpathyChatWindowPriv *priv;
1416 EmpathyChatWindow *new_window;
1419 priv = GET_PRIV (window);
1421 chat = priv->current_chat;
1422 new_window = empathy_chat_window_new ();
1424 empathy_chat_window_move_chat (window, new_window, chat);
1426 priv = GET_PRIV (new_window);
1427 gtk_widget_show (priv->dialog);
1431 chat_window_help_contents_activate_cb (GtkAction *action,
1432 EmpathyChatWindow *window)
1434 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1436 empathy_url_show (priv->dialog, "help:empathy");
1440 chat_window_help_about_activate_cb (GtkAction *action,
1441 EmpathyChatWindow *window)
1443 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1445 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1449 chat_window_delete_event_cb (GtkWidget *dialog,
1451 EmpathyChatWindow *window)
1453 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1454 EmpathyChat *chat = NULL;
1458 DEBUG ("Delete event received");
1460 for (l = priv->chats; l != NULL; l = l->next) {
1461 if (chat_needs_close_confirmation (l->data)) {
1468 confirm_close (window, TRUE, n_rooms,
1469 (n_rooms == 1 ? chat : NULL));
1471 remove_all_chats (window);
1478 chat_window_composing_cb (EmpathyChat *chat,
1479 gboolean is_composing,
1480 EmpathyChatWindow *window)
1482 chat_window_update_chat_tab (chat);
1486 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1489 EmpathyChatWindowPriv *priv;
1491 priv = GET_PRIV (window);
1493 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1497 chat_window_notification_closed_cb (NotifyNotification *notify,
1498 EmpathyChatWindow *self)
1500 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1502 g_object_unref (notify);
1503 if (priv->notification == notify) {
1504 priv->notification = NULL;
1509 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1510 EmpathyMessage *message,
1513 EmpathyContact *sender;
1514 const gchar *header;
1518 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1519 gboolean res, has_x_canonical_append;
1520 NotifyNotification *notification = priv->notification;
1522 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1525 res = g_settings_get_boolean (priv->gsettings_notif,
1526 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1533 sender = empathy_message_get_sender (message);
1534 header = empathy_contact_get_alias (sender);
1535 body = empathy_message_get_body (message);
1536 escaped = g_markup_escape_text (body, -1);
1537 has_x_canonical_append = empathy_notify_manager_has_capability (
1538 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1540 if (notification != NULL && !has_x_canonical_append) {
1541 /* if the notification server supports x-canonical-append, it is
1542 better to not use notify_notification_update to avoid
1543 overwriting the current notification message */
1544 notify_notification_update (notification,
1545 header, escaped, NULL);
1547 /* if the notification server supports x-canonical-append,
1548 the hint will be added, so that the message from the
1549 just created notification will be automatically appended
1550 to an existing notification with the same title.
1551 In this way the previous message will not be lost: the new
1552 message will appear below it, in the same notification */
1553 notification = notify_notification_new (header, escaped, NULL);
1555 if (priv->notification == NULL) {
1556 priv->notification = notification;
1559 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1561 tp_g_signal_connect_object (notification, "closed",
1562 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1564 if (has_x_canonical_append) {
1565 /* We have to set a not empty string to keep libnotify happy */
1566 notify_notification_set_hint_string (notification,
1567 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1571 const gchar *category = empathy_chat_is_room (chat)
1572 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1573 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1574 notify_notification_set_hint (notification,
1575 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1576 g_variant_new_string (category));
1580 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1581 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1583 if (pixbuf != NULL) {
1584 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1585 g_object_unref (pixbuf);
1588 notify_notification_show (notification, NULL);
1594 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1596 EmpathyChatWindowPriv *priv;
1599 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1601 priv = GET_PRIV (window);
1603 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1609 chat_window_new_message_cb (EmpathyChat *chat,
1610 EmpathyMessage *message,
1612 gboolean should_highlight,
1613 EmpathyChatWindow *window)
1615 EmpathyChatWindowPriv *priv;
1617 gboolean needs_urgency;
1618 EmpathyContact *sender;
1620 priv = GET_PRIV (window);
1622 has_focus = empathy_chat_window_has_focus (window);
1624 /* - if we're the sender, we play the sound if it's specified in the
1625 * preferences and we're not away.
1626 * - if we receive a message, we play the sound if it's specified in the
1627 * preferences and the window does not have focus on the chat receiving
1631 sender = empathy_message_get_sender (message);
1633 if (empathy_contact_is_user (sender)) {
1634 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1635 EMPATHY_SOUND_MESSAGE_OUTGOING);
1638 if (has_focus && priv->current_chat == chat) {
1639 /* window and tab are focused so consider the message to be read */
1641 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1642 empathy_chat_messages_read (chat);
1646 /* Update the chat tab if this is the first unread message */
1647 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1648 chat_window_update_chat_tab (chat);
1651 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1652 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1653 * an unamed MUC (msn-like).
1654 * In case of a MUC, we set urgency if either:
1655 * a) the chatroom's always_urgent property is TRUE
1656 * b) the message contains our alias
1658 if (empathy_chat_is_room (chat)) {
1661 EmpathyChatroom *chatroom;
1663 account = empathy_chat_get_account (chat);
1664 room = empathy_chat_get_id (chat);
1666 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1669 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1670 needs_urgency = TRUE;
1672 needs_urgency = should_highlight;
1675 needs_urgency = TRUE;
1678 if (needs_urgency) {
1680 chat_window_set_urgency_hint (window, TRUE);
1683 /* Pending messages have already been displayed and notified in the
1684 * approver, so we don't display a notification and play a sound for those */
1686 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1687 EMPATHY_SOUND_MESSAGE_INCOMING);
1689 chat_window_show_or_update_notification (window, message, chat);
1693 /* update the number of unread messages and the window icon */
1694 chat_window_title_update (priv);
1695 chat_window_icon_update (priv, TRUE);
1699 chat_window_command_part (EmpathyChat *chat,
1702 EmpathyChat *chat_to_be_parted;
1703 EmpathyTpChat *tp_chat = NULL;
1705 if (strv[1] == NULL) {
1706 /* No chatroom ID specified */
1707 tp_chat = empathy_chat_get_tp_chat (chat);
1709 empathy_tp_chat_leave (tp_chat, "");
1712 chat_to_be_parted = empathy_chat_window_find_chat (
1713 empathy_chat_get_account (chat), strv[1], FALSE);
1715 if (chat_to_be_parted != NULL) {
1716 /* Found a chatroom matching the specified ID */
1717 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1719 empathy_tp_chat_leave (tp_chat, strv[2]);
1723 /* Going by the syntax of PART command:
1725 * /PART [<chatroom-ID>] [<reason>]
1727 * Chatroom-ID is not a must to specify a reason.
1728 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1729 * MUC then the current chatroom should be parted and srtv[1] should
1730 * be treated as part of the optional part-message. */
1731 message = g_strconcat (strv[1], " ", strv[2], NULL);
1732 tp_chat = empathy_chat_get_tp_chat (chat);
1734 empathy_tp_chat_leave (tp_chat, message);
1740 static GtkNotebook *
1741 notebook_create_window_cb (GtkNotebook *source,
1747 EmpathyChatWindowPriv *priv;
1748 EmpathyChatWindow *window, *new_window;
1751 chat = EMPATHY_CHAT (page);
1752 window = chat_window_find_chat (chat);
1754 new_window = empathy_chat_window_new ();
1755 priv = GET_PRIV (new_window);
1757 DEBUG ("Detach hook called");
1759 empathy_chat_window_move_chat (window, new_window, chat);
1761 gtk_widget_show (priv->dialog);
1762 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1768 chat_window_page_switched_cb (GtkNotebook *notebook,
1771 EmpathyChatWindow *window)
1773 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1774 EmpathyChat *chat = EMPATHY_CHAT (child);
1776 DEBUG ("Page switched");
1778 if (priv->page_added) {
1779 priv->page_added = FALSE;
1780 empathy_chat_scroll_down (chat);
1782 else if (priv->current_chat == chat) {
1786 priv->current_chat = chat;
1787 empathy_chat_messages_read (chat);
1789 chat_window_update_chat_tab (chat);
1793 chat_window_page_added_cb (GtkNotebook *notebook,
1796 EmpathyChatWindow *window)
1798 EmpathyChatWindowPriv *priv;
1801 priv = GET_PRIV (window);
1803 /* If we just received DND to the same window, we don't want
1804 * to do anything here like removing the tab and then readding
1805 * it, so we return here and in "page-added".
1807 if (priv->dnd_same_window) {
1808 DEBUG ("Page added (back to the same window)");
1809 priv->dnd_same_window = FALSE;
1813 DEBUG ("Page added");
1815 /* Get chat object */
1816 chat = EMPATHY_CHAT (child);
1818 /* Connect chat signals for this window */
1819 g_signal_connect (chat, "composing",
1820 G_CALLBACK (chat_window_composing_cb),
1822 g_signal_connect (chat, "new-message",
1823 G_CALLBACK (chat_window_new_message_cb),
1825 g_signal_connect (chat, "part-command-entered",
1826 G_CALLBACK (chat_window_command_part),
1828 g_signal_connect (chat, "notify::tp-chat",
1829 G_CALLBACK (chat_window_update_chat_tab),
1832 /* Set flag so we know to perform some special operations on
1833 * switch page due to the new page being added.
1835 priv->page_added = TRUE;
1837 /* Get list of chats up to date */
1838 priv->chats = g_list_append (priv->chats, chat);
1840 chat_window_update_chat_tab (chat);
1844 chat_window_page_removed_cb (GtkNotebook *notebook,
1847 EmpathyChatWindow *window)
1849 EmpathyChatWindowPriv *priv;
1852 priv = GET_PRIV (window);
1854 /* If we just received DND to the same window, we don't want
1855 * to do anything here like removing the tab and then readding
1856 * it, so we return here and in "page-added".
1858 if (priv->dnd_same_window) {
1859 DEBUG ("Page removed (and will be readded to same window)");
1863 DEBUG ("Page removed");
1865 /* Get chat object */
1866 chat = EMPATHY_CHAT (child);
1868 /* Disconnect all signal handlers for this chat and this window */
1869 g_signal_handlers_disconnect_by_func (chat,
1870 G_CALLBACK (chat_window_composing_cb),
1872 g_signal_handlers_disconnect_by_func (chat,
1873 G_CALLBACK (chat_window_new_message_cb),
1875 g_signal_handlers_disconnect_by_func (chat,
1876 G_CALLBACK (chat_window_update_chat_tab),
1879 /* Keep list of chats up to date */
1880 priv->chats = g_list_remove (priv->chats, chat);
1881 empathy_chat_messages_read (chat);
1883 if (priv->chats == NULL) {
1884 g_object_unref (window);
1886 chat_window_update (window, TRUE);
1891 chat_window_focus_in_event_cb (GtkWidget *widget,
1893 EmpathyChatWindow *window)
1895 EmpathyChatWindowPriv *priv;
1897 priv = GET_PRIV (window);
1899 empathy_chat_messages_read (priv->current_chat);
1901 chat_window_set_urgency_hint (window, FALSE);
1903 /* Update the title, since we now mark all unread messages as read. */
1904 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1910 chat_window_drag_drop (GtkWidget *widget,
1911 GdkDragContext *context,
1915 EmpathyChatWindow *window)
1918 EmpathyChatWindowPriv *priv;
1920 priv = GET_PRIV (window);
1922 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1923 if (target == GDK_NONE)
1924 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1926 if (target != GDK_NONE) {
1927 gtk_drag_get_data (widget, context, target, time_);
1935 chat_window_drag_motion (GtkWidget *widget,
1936 GdkDragContext *context,
1940 EmpathyChatWindow *window)
1943 EmpathyChatWindowPriv *priv;
1945 priv = GET_PRIV (window);
1947 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1948 if (target != GDK_NONE) {
1949 /* This is a file drag. Ensure the contact is online and set the
1950 drag type to COPY. Note that it's possible that the tab will
1951 be switched by GTK+ after a timeout from drag_motion without
1952 getting another drag_motion to disable the drop. You have
1953 to hold your mouse really still.
1955 EmpathyContact *contact;
1957 priv = GET_PRIV (window);
1958 contact = empathy_chat_get_remote_contact (priv->current_chat);
1959 /* contact is NULL for multi-user chats. We don't do
1960 * file transfers to MUCs. We also don't send files
1961 * to offline contacts or contacts that don't support
1964 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1965 gdk_drag_status (context, 0, time_);
1968 if (!(empathy_contact_get_capabilities (contact)
1969 & EMPATHY_CAPABILITIES_FT)) {
1970 gdk_drag_status (context, 0, time_);
1973 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1977 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1978 if (target != GDK_NONE) {
1979 /* This is a drag of a contact from a contact list. Set to COPY.
1980 FIXME: If this drag is to a MUC window, it invites the user.
1981 Otherwise, it opens a chat. Should we use a different drag
1982 type for invites? Should we allow ASK?
1984 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1992 drag_data_received_individual_id (EmpathyChatWindow *self,
1994 GdkDragContext *context,
1997 GtkSelectionData *selection,
2002 EmpathyIndividualManager *manager = NULL;
2003 FolksIndividual *individual;
2004 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2005 EmpathyTpChat *chat;
2006 TpContact *tp_contact;
2008 EmpathyContact *contact;
2010 id = (const gchar *) gtk_selection_data_get_data (selection);
2012 DEBUG ("DND invididual %s", id);
2014 if (priv->current_chat == NULL)
2017 chat = empathy_chat_get_tp_chat (priv->current_chat);
2021 if (!empathy_tp_chat_can_add_contact (chat)) {
2022 DEBUG ("Can't invite contact to %s",
2023 tp_proxy_get_object_path (chat));
2027 manager = empathy_individual_manager_dup_singleton ();
2029 individual = empathy_individual_manager_lookup_member (manager, id);
2030 if (individual == NULL) {
2031 DEBUG ("Failed to find individual %s", id);
2035 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2036 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2037 if (tp_contact == NULL) {
2038 DEBUG ("Can't find a TpContact on connection %s for %s",
2039 tp_proxy_get_object_path (conn), id);
2043 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2044 tp_channel_get_identifier ((TpChannel *) chat));
2046 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2047 empathy_tp_chat_add (chat, contact, NULL);
2048 g_object_unref (contact);
2051 gtk_drag_finish (context, TRUE, FALSE, time_);
2052 tp_clear_object (&manager);
2056 chat_window_drag_data_received (GtkWidget *widget,
2057 GdkDragContext *context,
2060 GtkSelectionData *selection,
2063 EmpathyChatWindow *window)
2065 if (info == DND_DRAG_TYPE_CONTACT_ID) {
2066 EmpathyChat *chat = NULL;
2067 EmpathyChatWindow *old_window;
2068 TpAccount *account = NULL;
2069 EmpathyClientFactory *factory;
2072 const gchar *account_id;
2073 const gchar *contact_id;
2075 id = (const gchar*) gtk_selection_data_get_data (selection);
2077 factory = empathy_client_factory_dup ();
2079 DEBUG ("DND contact from roster with id:'%s'", id);
2081 strv = g_strsplit (id, ":", 2);
2082 if (g_strv_length (strv) == 2) {
2083 account_id = strv[0];
2084 contact_id = strv[1];
2086 tp_simple_client_factory_ensure_account (
2087 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2090 g_object_unref (factory);
2091 if (account != NULL)
2092 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2095 if (account == NULL) {
2097 gtk_drag_finish (context, FALSE, FALSE, time_);
2102 empathy_chat_with_contact_id (
2103 account, contact_id,
2104 empathy_get_current_action_time (),
2112 old_window = chat_window_find_chat (chat);
2114 if (old_window == window) {
2115 gtk_drag_finish (context, TRUE, FALSE, time_);
2119 empathy_chat_window_move_chat (old_window, window, chat);
2121 empathy_chat_window_add_chat (window, chat);
2124 /* Added to take care of any outstanding chat events */
2125 empathy_chat_window_present_chat (chat,
2126 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2128 /* We should return TRUE to remove the data when doing
2129 * GDK_ACTION_MOVE, but we don't here otherwise it has
2130 * weird consequences, and we handle that internally
2131 * anyway with add_chat () and remove_chat ().
2133 gtk_drag_finish (context, TRUE, FALSE, time_);
2135 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2136 drag_data_received_individual_id (window, widget, context, x, y,
2137 selection, info, time_);
2139 else if (info == DND_DRAG_TYPE_URI_LIST) {
2140 EmpathyChatWindowPriv *priv;
2141 EmpathyContact *contact;
2144 priv = GET_PRIV (window);
2145 contact = empathy_chat_get_remote_contact (priv->current_chat);
2147 /* contact is NULL when current_chat is a multi-user chat.
2148 * We don't do file transfers to MUCs, so just cancel the drag.
2150 if (contact == NULL) {
2151 gtk_drag_finish (context, TRUE, FALSE, time_);
2155 data = (const gchar *) gtk_selection_data_get_data (selection);
2156 empathy_send_file_from_uri_list (contact, data);
2158 gtk_drag_finish (context, TRUE, FALSE, time_);
2160 else if (info == DND_DRAG_TYPE_TAB) {
2162 EmpathyChatWindow *old_window = NULL;
2166 chat = (void *) gtk_selection_data_get_data (selection);
2167 old_window = chat_window_find_chat (*chat);
2170 EmpathyChatWindowPriv *priv;
2172 priv = GET_PRIV (window);
2173 priv->dnd_same_window = (old_window == window);
2174 DEBUG ("DND tab (within same window: %s)",
2175 priv->dnd_same_window ? "Yes" : "No");
2178 DEBUG ("DND from unknown source");
2179 gtk_drag_finish (context, FALSE, FALSE, time_);
2184 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2185 guint num_chats_in_manager,
2186 EmpathyChatWindow *window)
2188 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2190 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2191 num_chats_in_manager > 0);
2195 chat_window_finalize (GObject *object)
2197 EmpathyChatWindow *window;
2198 EmpathyChatWindowPriv *priv;
2200 window = EMPATHY_CHAT_WINDOW (object);
2201 priv = GET_PRIV (window);
2203 DEBUG ("Finalized: %p", object);
2205 g_object_unref (priv->ui_manager);
2206 g_object_unref (priv->chatroom_manager);
2207 g_object_unref (priv->notify_mgr);
2208 g_object_unref (priv->gsettings_chat);
2209 g_object_unref (priv->gsettings_notif);
2210 g_object_unref (priv->gsettings_ui);
2211 g_object_unref (priv->sound_mgr);
2213 if (priv->notification != NULL) {
2214 notify_notification_close (priv->notification, NULL);
2215 priv->notification = NULL;
2218 if (priv->contact_targets) {
2219 gtk_target_list_unref (priv->contact_targets);
2221 if (priv->file_targets) {
2222 gtk_target_list_unref (priv->file_targets);
2225 if (priv->chat_manager) {
2226 g_signal_handler_disconnect (priv->chat_manager,
2227 priv->chat_manager_chats_changed_id);
2228 g_object_unref (priv->chat_manager);
2229 priv->chat_manager = NULL;
2232 chat_windows = g_list_remove (chat_windows, window);
2233 gtk_widget_destroy (priv->dialog);
2235 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2239 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2241 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2243 object_class->finalize = chat_window_finalize;
2245 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2249 empathy_chat_window_init (EmpathyChatWindow *window)
2252 GtkAccelGroup *accel_group;
2257 GtkWidget *chat_vbox;
2259 EmpathySmileyManager *smiley_manager;
2260 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2261 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2263 window->priv = priv;
2264 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2265 gui = empathy_builder_get_file (filename,
2266 "chat_window", &priv->dialog,
2267 "chat_vbox", &chat_vbox,
2268 "ui_manager", &priv->ui_manager,
2269 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2270 "menu_conv_favorite", &priv->menu_conv_favorite,
2271 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2272 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2273 "menu_edit_cut", &priv->menu_edit_cut,
2274 "menu_edit_copy", &priv->menu_edit_copy,
2275 "menu_edit_paste", &priv->menu_edit_paste,
2276 "menu_edit_find", &priv->menu_edit_find,
2277 "menu_tabs_next", &priv->menu_tabs_next,
2278 "menu_tabs_prev", &priv->menu_tabs_prev,
2279 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2280 "menu_tabs_left", &priv->menu_tabs_left,
2281 "menu_tabs_right", &priv->menu_tabs_right,
2282 "menu_tabs_detach", &priv->menu_tabs_detach,
2286 empathy_builder_connect (gui, window,
2287 "menu_conv", "activate", chat_window_conv_activate_cb,
2288 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2289 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2290 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2291 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2292 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2293 "menu_conv_close", "activate", chat_window_close_activate_cb,
2294 "menu_edit", "activate", chat_window_edit_activate_cb,
2295 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2296 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2297 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2298 "menu_edit_find", "activate", chat_window_find_activate_cb,
2299 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2300 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2301 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2302 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2303 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2304 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2305 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2306 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2309 g_object_ref (priv->ui_manager);
2310 g_object_unref (gui);
2312 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2313 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2314 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2315 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2317 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2319 priv->notebook = gtk_notebook_new ();
2321 g_signal_connect (priv->notebook, "create-window",
2322 G_CALLBACK (notebook_create_window_cb), window);
2324 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2325 "EmpathyChatWindow");
2326 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2327 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2328 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2329 gtk_widget_show (priv->notebook);
2332 accel_group = gtk_accel_group_new ();
2333 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2335 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2336 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2339 gtk_accel_group_connect (accel_group,
2346 g_object_unref (accel_group);
2348 /* Set up drag target lists */
2349 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2350 G_N_ELEMENTS (drag_types_dest_contact));
2351 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2352 G_N_ELEMENTS (drag_types_dest_file));
2354 /* Set up smiley menu */
2355 smiley_manager = empathy_smiley_manager_dup_singleton ();
2356 submenu = empathy_smiley_menu_new (smiley_manager,
2357 chat_window_insert_smiley_activate_cb,
2359 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2360 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2361 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2362 g_object_unref (smiley_manager);
2364 /* Set up signals we can't do with ui file since we may need to
2365 * block/unblock them at some later stage.
2368 g_signal_connect (priv->dialog,
2370 G_CALLBACK (chat_window_delete_event_cb),
2372 g_signal_connect (priv->dialog,
2374 G_CALLBACK (chat_window_focus_in_event_cb),
2376 g_signal_connect_after (priv->notebook,
2378 G_CALLBACK (chat_window_page_switched_cb),
2380 g_signal_connect (priv->notebook,
2382 G_CALLBACK (chat_window_page_added_cb),
2384 g_signal_connect (priv->notebook,
2386 G_CALLBACK (chat_window_page_removed_cb),
2389 /* Set up drag and drop */
2390 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2391 GTK_DEST_DEFAULT_HIGHLIGHT,
2393 G_N_ELEMENTS (drag_types_dest),
2394 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2396 /* connect_after to allow GtkNotebook's built-in tab switching */
2397 g_signal_connect_after (priv->notebook,
2399 G_CALLBACK (chat_window_drag_motion),
2401 g_signal_connect (priv->notebook,
2402 "drag-data-received",
2403 G_CALLBACK (chat_window_drag_data_received),
2405 g_signal_connect (priv->notebook,
2407 G_CALLBACK (chat_window_drag_drop),
2410 chat_windows = g_list_prepend (chat_windows, window);
2412 /* Set up private details */
2414 priv->current_chat = NULL;
2415 priv->notification = NULL;
2417 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2419 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2420 priv->chat_manager_chats_changed_id =
2421 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2422 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2425 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2426 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2430 /* Returns the window to open a new tab in if there is a suitable window,
2431 * otherwise, returns NULL indicating that a new window should be added.
2433 static EmpathyChatWindow *
2434 empathy_chat_window_get_default (gboolean room)
2436 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2438 gboolean separate_windows = TRUE;
2440 separate_windows = g_settings_get_boolean (gsettings,
2441 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2443 g_object_unref (gsettings);
2445 if (separate_windows) {
2446 /* Always create a new window */
2450 for (l = chat_windows; l; l = l->next) {
2451 EmpathyChatWindow *chat_window;
2452 guint nb_rooms, nb_private;
2454 chat_window = l->data;
2456 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2458 /* Skip the window if there aren't any rooms in it */
2459 if (room && nb_rooms == 0)
2462 /* Skip the window if there aren't any 1-1 chats in it */
2463 if (!room && nb_private == 0)
2473 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2476 EmpathyChatWindowPriv *priv;
2478 GtkWidget *popup_label;
2480 GValue value = { 0, };
2482 g_return_if_fail (window != NULL);
2483 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2485 priv = GET_PRIV (window);
2487 /* Reference the chat object */
2488 g_object_ref (chat);
2490 /* If this window has just been created, position it */
2491 if (priv->chats == NULL) {
2492 const gchar *name = "chat-window";
2493 gboolean separate_windows;
2495 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2496 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2498 if (empathy_chat_is_room (chat))
2499 name = "room-window";
2501 if (separate_windows) {
2504 /* Save current position of the window */
2505 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2507 /* First bind to the 'generic' name. So new window for which we didn't
2508 * save a geometry yet will have the geometry of the last saved
2509 * window (bgo #601191). */
2510 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2512 /* Restore previous position of the window so the newly created window
2513 * won't be in the same position as the latest saved window and so
2514 * completely hide it. */
2515 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2517 /* Then bind it to the name of the contact/room so we'll save the
2518 * geometry specific to this window */
2519 name = empathy_chat_get_id (chat);
2522 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2525 child = GTK_WIDGET (chat);
2526 label = chat_window_create_label (window, chat, TRUE);
2527 popup_label = chat_window_create_label (window, chat, FALSE);
2528 gtk_widget_show (child);
2530 g_signal_connect (chat, "notify::name",
2531 G_CALLBACK (chat_window_chat_notify_cb),
2533 g_signal_connect (chat, "notify::subject",
2534 G_CALLBACK (chat_window_chat_notify_cb),
2536 g_signal_connect (chat, "notify::remote-contact",
2537 G_CALLBACK (chat_window_chat_notify_cb),
2539 g_signal_connect (chat, "notify::sms-channel",
2540 G_CALLBACK (chat_window_chat_notify_cb),
2542 g_signal_connect (chat, "notify::n-messages-sending",
2543 G_CALLBACK (chat_window_chat_notify_cb),
2545 g_signal_connect (chat, "notify::nb-unread-messages",
2546 G_CALLBACK (chat_window_chat_notify_cb),
2548 chat_window_chat_notify_cb (chat);
2550 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2551 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2552 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2553 g_value_init (&value, G_TYPE_BOOLEAN);
2554 g_value_set_boolean (&value, TRUE);
2555 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2556 child, "tab-expand" , &value);
2557 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2558 child, "tab-fill" , &value);
2559 g_value_unset (&value);
2561 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2565 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2568 EmpathyChatWindowPriv *priv;
2570 EmpathyContact *remote_contact;
2571 EmpathyChatManager *chat_manager;
2573 g_return_if_fail (window != NULL);
2574 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2576 priv = GET_PRIV (window);
2578 g_signal_handlers_disconnect_by_func (chat,
2579 chat_window_chat_notify_cb,
2581 remote_contact = g_object_get_data (G_OBJECT (chat),
2582 "chat-window-remote-contact");
2583 if (remote_contact) {
2584 g_signal_handlers_disconnect_by_func (remote_contact,
2585 chat_window_update_chat_tab,
2589 chat_manager = empathy_chat_manager_dup_singleton ();
2590 empathy_chat_manager_closed_chat (chat_manager, chat);
2591 g_object_unref (chat_manager);
2593 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2595 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2597 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2599 g_object_unref (chat);
2603 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2604 EmpathyChatWindow *new_window,
2609 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2610 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2611 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2613 widget = GTK_WIDGET (chat);
2615 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2616 G_OBJECT (widget)->ref_count);
2618 /* We reference here to make sure we don't loose the widget
2619 * and the EmpathyChat object during the move.
2621 g_object_ref (chat);
2622 g_object_ref (widget);
2624 empathy_chat_window_remove_chat (old_window, chat);
2625 empathy_chat_window_add_chat (new_window, chat);
2627 g_object_unref (widget);
2628 g_object_unref (chat);
2632 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2635 EmpathyChatWindowPriv *priv;
2638 g_return_if_fail (window != NULL);
2639 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2641 priv = GET_PRIV (window);
2643 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2645 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2650 empathy_chat_window_find_chat (TpAccount *account,
2652 gboolean sms_channel)
2656 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2658 for (l = chat_windows; l; l = l->next) {
2659 EmpathyChatWindowPriv *priv;
2660 EmpathyChatWindow *window;
2664 priv = GET_PRIV (window);
2666 for (ll = priv->chats; ll; ll = ll->next) {
2671 if (account == empathy_chat_get_account (chat) &&
2672 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2673 sms_channel == empathy_chat_is_sms_channel (chat)) {
2683 empathy_chat_window_present_chat (EmpathyChat *chat,
2686 EmpathyChatWindow *window;
2687 EmpathyChatWindowPriv *priv;
2688 guint32 x_timestamp;
2690 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2692 window = chat_window_find_chat (chat);
2694 /* If the chat has no window, create one */
2695 if (window == NULL) {
2696 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2698 window = empathy_chat_window_new ();
2700 /* we want to display the newly created window even if we don't present
2702 priv = GET_PRIV (window);
2703 gtk_widget_show (priv->dialog);
2706 empathy_chat_window_add_chat (window, chat);
2709 /* Don't force the window to show itself when it wasn't
2710 * an action by the user
2712 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2715 priv = GET_PRIV (window);
2717 if (x_timestamp != GDK_CURRENT_TIME) {
2718 /* Don't present or switch tab if the action was earlier than the
2719 * last actions X time, accounting for overflow and the first ever
2722 if (priv->x_user_action_time != 0
2723 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2726 priv->x_user_action_time = x_timestamp;
2729 empathy_chat_window_switch_to_chat (window, chat);
2731 /* Don't use empathy_window_present_with_time () which would move the window
2732 * to our current desktop but move to the window's desktop instead. This is
2733 * more coherent with Shell's 'app is ready' notication which moves the view
2734 * to the app desktop rather than moving the app itself. */
2735 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2737 gtk_widget_grab_focus (chat->input_text_view);
2741 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2745 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2747 guint _nb_rooms = 0, _nb_private = 0;
2749 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2750 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2756 if (nb_rooms != NULL)
2757 *nb_rooms = _nb_rooms;
2758 if (nb_private != NULL)
2759 *nb_private = _nb_private;