1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2003-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Richard Hult <richard@imendio.com>
23 * Martyn Russell <martyn@imendio.com>
24 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
25 * Xavier Claessens <xclaesse@gmail.com>
26 * Rômulo Fernandes Machado <romulo@castorgroup.net>
34 #include <gdk/gdkkeysyms.h>
36 #include <glib/gi18n.h>
37 #include <libnotify/notification.h>
39 #include <telepathy-glib/telepathy-glib.h>
41 #include <libempathy/empathy-client-factory.h>
42 #include <libempathy/empathy-contact.h>
43 #include <libempathy/empathy-message.h>
44 #include <libempathy/empathy-chatroom-manager.h>
45 #include <libempathy/empathy-gsettings.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-request-util.h>
48 #include <libempathy/empathy-individual-manager.h>
50 #include <libempathy-gtk/empathy-images.h>
51 #include <libempathy-gtk/empathy-contact-dialogs.h>
52 #include <libempathy-gtk/empathy-log-window.h>
53 #include <libempathy-gtk/empathy-geometry.h>
54 #include <libempathy-gtk/empathy-smiley-manager.h>
55 #include <libempathy-gtk/empathy-sound-manager.h>
56 #include <libempathy-gtk/empathy-ui-utils.h>
57 #include <libempathy-gtk/empathy-notify-manager.h>
59 #include "empathy-chat-manager.h"
60 #include "empathy-chat-window.h"
61 #include "empathy-about-dialog.h"
62 #include "empathy-invite-participant-dialog.h"
64 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
65 #include <libempathy/empathy-debug.h>
67 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
69 #define X_EARLIER_OR_EQL(t1, t2) \
70 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
71 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
74 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
76 EmpathyChat *current_chat;
79 gboolean dnd_same_window;
80 EmpathyChatroomManager *chatroom_manager;
81 EmpathyNotifyManager *notify_mgr;
84 NotifyNotification *notification;
86 GtkTargetList *contact_targets;
87 GtkTargetList *file_targets;
89 EmpathyChatManager *chat_manager;
90 gulong chat_manager_chats_changed_id;
93 GtkUIManager *ui_manager;
94 GtkAction *menu_conv_insert_smiley;
95 GtkAction *menu_conv_favorite;
96 GtkAction *menu_conv_always_urgent;
97 GtkAction *menu_conv_toggle_contacts;
99 GtkAction *menu_edit_cut;
100 GtkAction *menu_edit_copy;
101 GtkAction *menu_edit_paste;
102 GtkAction *menu_edit_find;
104 GtkAction *menu_tabs_next;
105 GtkAction *menu_tabs_prev;
106 GtkAction *menu_tabs_undo_close_tab;
107 GtkAction *menu_tabs_left;
108 GtkAction *menu_tabs_right;
109 GtkAction *menu_tabs_detach;
111 /* Last user action time we acted upon to show a tab */
112 guint32 x_user_action_time;
114 GSettings *gsettings_chat;
115 GSettings *gsettings_notif;
116 GSettings *gsettings_ui;
118 EmpathySoundManager *sound_mgr;
119 } EmpathyChatWindowPriv;
121 static GList *chat_windows = NULL;
123 static const guint tab_accel_keys[] = {
124 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
125 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
129 DND_DRAG_TYPE_CONTACT_ID,
130 DND_DRAG_TYPE_INDIVIDUAL_ID,
131 DND_DRAG_TYPE_URI_LIST,
135 static const GtkTargetEntry drag_types_dest[] = {
136 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
137 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
138 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
139 /* FIXME: disabled because of bug #640513
140 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
141 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
145 static const GtkTargetEntry drag_types_dest_contact[] = {
146 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
147 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
150 static const GtkTargetEntry drag_types_dest_file[] = {
151 /* must be first to be prioritized, in order to receive the
152 * note's file path from Tomboy instead of an URI */
153 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
154 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
157 static void chat_window_update (EmpathyChatWindow *window,
158 gboolean update_contact_menu);
160 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
163 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
166 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
167 EmpathyChatWindow *new_window,
170 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
174 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
177 chat_window_accel_cb (GtkAccelGroup *accelgroup,
181 EmpathyChatWindow *window)
183 EmpathyChatWindowPriv *priv;
187 priv = GET_PRIV (window);
189 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
190 if (tab_accel_keys[i] == key) {
197 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
201 static EmpathyChatWindow *
202 chat_window_find_chat (EmpathyChat *chat)
204 EmpathyChatWindowPriv *priv;
207 for (l = chat_windows; l; l = l->next) {
208 priv = GET_PRIV (l->data);
209 ll = g_list_find (priv->chats, chat);
219 remove_all_chats (EmpathyChatWindow *window)
221 EmpathyChatWindowPriv *priv;
223 priv = GET_PRIV (window);
224 g_object_ref (window);
226 while (priv->chats) {
227 empathy_chat_window_remove_chat (window, priv->chats->data);
230 g_object_unref (window);
234 confirm_close_response_cb (GtkWidget *dialog,
236 EmpathyChatWindow *window)
240 chat = g_object_get_data (G_OBJECT (dialog), "chat");
242 gtk_widget_destroy (dialog);
244 if (response != GTK_RESPONSE_ACCEPT)
248 empathy_chat_window_remove_chat (window, chat);
250 remove_all_chats (window);
255 confirm_close (EmpathyChatWindow *window,
256 gboolean close_window,
260 EmpathyChatWindowPriv *priv;
262 gchar *primary, *secondary;
264 g_return_if_fail (n_rooms > 0);
267 g_return_if_fail (chat == NULL);
269 g_return_if_fail (chat != NULL);
272 priv = GET_PRIV (window);
274 /* If there are no chats in this window, how could we possibly have got
277 g_return_if_fail (priv->chats != NULL);
279 /* Treat closing a window which only has one tab exactly like closing
282 if (close_window && priv->chats->next == NULL) {
283 close_window = FALSE;
284 chat = priv->chats->data;
288 primary = g_strdup (_("Close this window?"));
291 gchar *chat_name = empathy_chat_dup_name (chat);
292 secondary = g_strdup_printf (
293 _("Closing this window will leave %s. You will "
294 "not receive any further messages until you "
299 secondary = g_strdup_printf (
300 /* Note to translators: the number of chats will
301 * always be at least 2.
304 "Closing this window will leave a chat room. You will "
305 "not receive any further messages until you rejoin it.",
306 "Closing this window will leave %u chat rooms. You will "
307 "not receive any further messages until you rejoin them.",
312 gchar *chat_name = empathy_chat_dup_name (chat);
313 primary = g_strdup_printf (_("Leave %s?"), chat_name);
314 secondary = g_strdup (_("You will not receive any further messages from this chat "
315 "room until you rejoin it."));
319 dialog = gtk_message_dialog_new (
320 GTK_WINDOW (priv->dialog),
321 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
326 gtk_window_set_title (GTK_WINDOW (dialog), "");
327 g_object_set (dialog, "secondary-text", secondary, NULL);
332 gtk_dialog_add_button (GTK_DIALOG (dialog),
333 close_window ? _("Close window") : _("Leave room"),
334 GTK_RESPONSE_ACCEPT);
335 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
336 GTK_RESPONSE_ACCEPT);
339 g_object_set_data (G_OBJECT (dialog), "chat", chat);
342 g_signal_connect (dialog, "response",
343 G_CALLBACK (confirm_close_response_cb), window);
345 gtk_window_present (GTK_WINDOW (dialog));
348 /* Returns TRUE if we should check if the user really wants to leave. If it's
349 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
350 * the user is actually in the room as opposed to having been kicked or gone
351 * offline or something), then we should check.
354 chat_needs_close_confirmation (EmpathyChat *chat)
356 return (empathy_chat_is_room (chat)
357 && empathy_chat_get_tp_chat (chat) != NULL);
361 maybe_close_chat (EmpathyChatWindow *window,
364 g_return_if_fail (chat != NULL);
366 if (chat_needs_close_confirmation (chat)) {
367 confirm_close (window, FALSE, 1, chat);
369 empathy_chat_window_remove_chat (window, chat);
374 chat_window_close_clicked_cb (GtkAction *action,
377 EmpathyChatWindow *window;
379 window = chat_window_find_chat (chat);
380 maybe_close_chat (window, chat);
384 chat_tab_style_updated_cb (GtkWidget *hbox,
388 int char_width, h, w;
389 PangoContext *context;
390 const PangoFontDescription *font_desc;
391 PangoFontMetrics *metrics;
393 button = g_object_get_data (G_OBJECT (user_data),
394 "chat-window-tab-close-button");
395 context = gtk_widget_get_pango_context (hbox);
397 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
398 GTK_STATE_FLAG_NORMAL);
400 metrics = pango_context_get_metrics (context, font_desc,
401 pango_context_get_language (context));
402 char_width = pango_font_metrics_get_approximate_char_width (metrics);
403 pango_font_metrics_unref (metrics);
405 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
406 GTK_ICON_SIZE_MENU, &w, &h);
408 /* Request at least about 12 chars width plus at least space for the status
409 * image and the close button */
410 gtk_widget_set_size_request (hbox,
411 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
413 gtk_widget_set_size_request (button, w, h);
417 create_close_button (void)
419 GtkWidget *button, *image;
420 GtkStyleContext *context;
422 button = gtk_button_new ();
424 context = gtk_widget_get_style_context (button);
425 gtk_style_context_add_class (context, "empathy-tab-close-button");
427 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
428 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
430 /* We don't want focus/keynav for the button to avoid clutter, and
431 * Ctrl-W works anyway.
433 gtk_widget_set_can_focus (button, FALSE);
434 gtk_widget_set_can_default (button, FALSE);
436 image = gtk_image_new_from_icon_name ("window-close-symbolic",
438 gtk_widget_show (image);
440 gtk_container_add (GTK_CONTAINER (button), image);
446 chat_window_create_label (EmpathyChatWindow *window,
448 gboolean is_tab_label)
451 GtkWidget *name_label;
452 GtkWidget *status_image;
453 GtkWidget *event_box;
454 GtkWidget *event_box_hbox;
455 PangoAttrList *attr_list;
456 PangoAttribute *attr;
458 /* The spacing between the button and the label. */
459 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
461 event_box = gtk_event_box_new ();
462 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
464 name_label = gtk_label_new (NULL);
466 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
468 attr_list = pango_attr_list_new ();
469 attr = pango_attr_scale_new (1/1.2);
470 attr->start_index = 0;
471 attr->end_index = -1;
472 pango_attr_list_insert (attr_list, attr);
473 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
474 pango_attr_list_unref (attr_list);
476 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
477 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
478 g_object_set_data (G_OBJECT (chat),
479 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
482 status_image = gtk_image_new ();
484 /* Spacing between the icon and label. */
485 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
487 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
488 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
490 g_object_set_data (G_OBJECT (chat),
491 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
493 g_object_set_data (G_OBJECT (chat),
494 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
497 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
498 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
501 GtkWidget *close_button;
502 GtkWidget *sending_spinner;
504 sending_spinner = gtk_spinner_new ();
506 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
508 g_object_set_data (G_OBJECT (chat),
509 "chat-window-tab-sending-spinner",
512 close_button = create_close_button ();
513 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
515 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
517 g_signal_connect (close_button,
519 G_CALLBACK (chat_window_close_clicked_cb),
522 /* React to theme changes and also setup the size correctly. */
523 g_signal_connect (hbox,
525 G_CALLBACK (chat_tab_style_updated_cb),
529 gtk_widget_show_all (hbox);
535 _submenu_notify_visible_changed_cb (GObject *object,
539 g_signal_handlers_disconnect_by_func (object,
540 _submenu_notify_visible_changed_cb,
542 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
546 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
551 gboolean wrap_around;
552 gboolean is_connected;
555 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
556 first_page = (page_num == 0);
557 last_page = (page_num == (num_pages - 1));
558 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
560 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
562 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
564 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
566 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
567 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
568 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
569 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
573 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
574 EmpathyChatWindow *self)
576 EmpathyTpChat *tp_chat;
577 TpConnection *connection;
579 gboolean sensitive = FALSE;
581 g_return_if_fail (priv->current_chat != NULL);
583 action = gtk_ui_manager_get_action (priv->ui_manager,
584 "/chats_menubar/menu_conv/menu_conv_invite_participant");
585 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
587 if (tp_chat != NULL) {
588 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
590 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
591 (tp_connection_get_status (connection, NULL) ==
592 TP_CONNECTION_STATUS_CONNECTED);
595 gtk_action_set_sensitive (action, sensitive);
599 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
600 EmpathyChatWindow *window)
602 GtkWidget *menu, *submenu, *orig_submenu;
604 menu = gtk_ui_manager_get_widget (priv->ui_manager,
605 "/chats_menubar/menu_contact");
606 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
608 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
609 submenu = empathy_chat_get_contact_menu (priv->current_chat);
611 if (submenu != NULL) {
612 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
613 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
615 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
616 gtk_widget_show (menu);
617 gtk_widget_set_sensitive (menu, TRUE);
619 gtk_widget_set_sensitive (menu, FALSE);
622 tp_g_signal_connect_object (orig_submenu,
624 (GCallback)_submenu_notify_visible_changed_cb,
630 get_all_unread_messages (EmpathyChatWindowPriv *priv)
635 for (l = priv->chats; l != NULL; l = g_list_next (l))
636 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
642 get_window_title_name (EmpathyChatWindowPriv *priv)
644 gchar *active_name, *ret;
646 guint current_unread_msgs;
648 nb_chats = g_list_length (priv->chats);
649 g_assert (nb_chats > 0);
651 active_name = empathy_chat_dup_name (priv->current_chat);
653 current_unread_msgs = empathy_chat_get_nb_unread_messages (
658 if (current_unread_msgs == 0)
659 ret = g_strdup (active_name);
661 ret = g_strdup_printf (ngettext (
663 "%s (%d unread)", current_unread_msgs),
664 active_name, current_unread_msgs);
666 guint nb_others = nb_chats - 1;
667 guint all_unread_msgs;
669 all_unread_msgs = get_all_unread_messages (priv);
671 if (all_unread_msgs == 0) {
672 /* no unread message */
673 ret = g_strdup_printf (ngettext (
675 "%s (and %u others)", nb_others),
676 active_name, nb_others);
679 else if (all_unread_msgs == current_unread_msgs) {
680 /* unread messages are in the current tab */
681 ret = g_strdup_printf (ngettext (
683 "%s (%d unread)", current_unread_msgs),
684 active_name, current_unread_msgs);
687 else if (current_unread_msgs == 0) {
688 /* unread messages are in other tabs */
689 ret = g_strdup_printf (ngettext (
690 "%s (%d unread from others)",
691 "%s (%d unread from others)",
693 active_name, all_unread_msgs);
697 /* unread messages are in all the tabs */
698 ret = g_strdup_printf (ngettext (
699 "%s (%d unread from all)",
700 "%s (%d unread from all)",
702 active_name, all_unread_msgs);
706 g_free (active_name);
712 chat_window_title_update (EmpathyChatWindowPriv *priv)
716 name = get_window_title_name (priv);
717 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
722 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
725 EmpathyContact *remote_contact;
726 gboolean avatar_in_icon;
729 n_chats = g_list_length (priv->chats);
731 /* Update window icon */
733 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
734 EMPATHY_IMAGE_MESSAGE);
736 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
737 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
739 if (n_chats == 1 && avatar_in_icon) {
740 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
741 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
742 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
745 g_object_unref (icon);
748 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
754 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
758 GtkWidget *chat_close_button;
761 if (num_pages == 1) {
762 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
763 chat_close_button = g_object_get_data (G_OBJECT (chat),
764 "chat-window-tab-close-button");
765 gtk_widget_hide (chat_close_button);
767 for (i=0; i<num_pages; i++) {
768 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
769 chat_close_button = g_object_get_data (G_OBJECT (chat),
770 "chat-window-tab-close-button");
771 gtk_widget_show (chat_close_button);
777 chat_window_update (EmpathyChatWindow *window,
778 gboolean update_contact_menu)
780 EmpathyChatWindowPriv *priv = GET_PRIV (window);
783 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
785 /* Update Tab menu */
786 chat_window_menu_context_update (priv,
789 chat_window_conversation_menu_update (priv, window);
791 /* If this update is due to a focus-in event, we know the menu will be
792 the same as when we last left it, so no work to do. Besides, if we
793 swap out the menu on a focus-in, we may confuse any external global
795 if (update_contact_menu) {
796 chat_window_contact_menu_update (priv,
800 chat_window_title_update (priv);
802 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
804 chat_window_close_button_update (priv,
809 append_markup_printf (GString *string,
816 va_start (args, format);
818 tmp = g_markup_vprintf_escaped (format, args);
819 g_string_append (string, tmp);
826 chat_window_update_chat_tab_full (EmpathyChat *chat,
827 gboolean update_contact_menu)
829 EmpathyChatWindow *window;
830 EmpathyChatWindowPriv *priv;
831 EmpathyContact *remote_contact;
835 const gchar *subject;
836 const gchar *status = NULL;
840 const gchar *icon_name;
841 GtkWidget *tab_image;
842 GtkWidget *menu_image;
843 GtkWidget *sending_spinner;
846 window = chat_window_find_chat (chat);
850 priv = GET_PRIV (window);
852 /* Get information */
853 name = empathy_chat_dup_name (chat);
854 account = empathy_chat_get_account (chat);
855 subject = empathy_chat_get_subject (chat);
856 remote_contact = empathy_chat_get_remote_contact (chat);
858 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
859 name, tp_proxy_get_object_path (account), subject, remote_contact);
861 /* Update tab image */
862 if (empathy_chat_get_tp_chat (chat) == NULL) {
863 /* No TpChat, we are disconnected */
866 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
867 icon_name = EMPATHY_IMAGE_MESSAGE;
869 else if (remote_contact && empathy_chat_is_composing (chat)) {
870 icon_name = EMPATHY_IMAGE_TYPING;
872 else if (empathy_chat_is_sms_channel (chat)) {
873 icon_name = EMPATHY_IMAGE_SMS;
875 else if (remote_contact) {
876 icon_name = empathy_icon_name_for_contact (remote_contact);
878 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
881 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
882 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
883 if (icon_name != NULL) {
884 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
885 gtk_widget_show (tab_image);
886 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
887 gtk_widget_show (menu_image);
889 gtk_widget_hide (tab_image);
890 gtk_widget_hide (menu_image);
893 /* Update the sending spinner */
894 nb_sending = empathy_chat_get_n_messages_sending (chat);
895 sending_spinner = g_object_get_data (G_OBJECT (chat),
896 "chat-window-tab-sending-spinner");
898 g_object_set (sending_spinner,
899 "active", nb_sending > 0,
900 "visible", nb_sending > 0,
903 /* Update tab tooltip */
904 tooltip = g_string_new (NULL);
906 if (remote_contact) {
907 id = empathy_contact_get_id (remote_contact);
908 status = empathy_contact_get_presence_message (remote_contact);
913 if (empathy_chat_is_sms_channel (chat)) {
914 append_markup_printf (tooltip, "%s ", _("SMS:"));
917 append_markup_printf (tooltip,
918 "<b>%s</b><small> (%s)</small>",
920 tp_account_get_display_name (account));
922 if (nb_sending > 0) {
923 char *tmp = g_strdup_printf (
924 ngettext ("Sending %d message",
925 "Sending %d messages",
929 g_string_append (tooltip, "\n");
930 g_string_append (tooltip, tmp);
932 gtk_widget_set_tooltip_text (sending_spinner, tmp);
936 if (!EMP_STR_EMPTY (status)) {
937 append_markup_printf (tooltip, "\n<i>%s</i>", status);
941 append_markup_printf (tooltip, "\n<b>%s</b> %s",
942 _("Topic:"), subject);
945 if (remote_contact && empathy_chat_is_composing (chat)) {
946 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
949 if (remote_contact != NULL) {
950 const gchar * const *types;
952 types = empathy_contact_get_client_types (remote_contact);
953 if (types != NULL && !tp_strdiff (types[0], "phone")) {
954 /* I'm on a phone ! */
957 name = g_strdup_printf ("☎ %s", name);
962 markup = g_string_free (tooltip, FALSE);
963 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
964 gtk_widget_set_tooltip_markup (widget, markup);
965 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
966 gtk_widget_set_tooltip_markup (widget, markup);
969 /* Update tab and menu label */
970 if (empathy_chat_is_highlighted (chat)) {
971 markup = g_markup_printf_escaped (
972 "<span color=\"red\" weight=\"bold\">%s</span>",
975 markup = g_markup_escape_text (name, -1);
978 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
979 gtk_label_set_markup (GTK_LABEL (widget), markup);
980 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
981 gtk_label_set_markup (GTK_LABEL (widget), markup);
984 /* Update the window if it's the current chat */
985 if (priv->current_chat == chat) {
986 chat_window_update (window, update_contact_menu);
993 chat_window_update_chat_tab (EmpathyChat *chat)
995 chat_window_update_chat_tab_full (chat, TRUE);
999 chat_window_chat_notify_cb (EmpathyChat *chat)
1001 EmpathyChatWindow *window;
1002 EmpathyContact *old_remote_contact;
1003 EmpathyContact *remote_contact = NULL;
1005 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
1006 remote_contact = empathy_chat_get_remote_contact (chat);
1008 if (old_remote_contact != remote_contact) {
1009 /* The remote-contact associated with the chat changed, we need
1010 * to keep track of any change of that contact and update the
1011 * window each time. */
1012 if (remote_contact) {
1013 g_signal_connect_swapped (remote_contact, "notify",
1014 G_CALLBACK (chat_window_update_chat_tab),
1017 if (old_remote_contact) {
1018 g_signal_handlers_disconnect_by_func (old_remote_contact,
1019 chat_window_update_chat_tab,
1023 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1024 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1027 chat_window_update_chat_tab (chat);
1029 window = chat_window_find_chat (chat);
1030 if (window != NULL) {
1031 chat_window_update (window, FALSE);
1036 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1037 EmpathySmiley *smiley,
1040 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1042 GtkTextBuffer *buffer;
1045 chat = priv->current_chat;
1047 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1048 gtk_text_buffer_get_end_iter (buffer, &iter);
1049 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1053 chat_window_conv_activate_cb (GtkAction *action,
1054 EmpathyChatWindow *window)
1056 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1059 EmpathyContact *remote_contact = NULL;
1061 /* Favorite room menu */
1062 is_room = empathy_chat_is_room (priv->current_chat);
1066 gboolean found = FALSE;
1067 EmpathyChatroom *chatroom;
1069 room = empathy_chat_get_id (priv->current_chat);
1070 account = empathy_chat_get_account (priv->current_chat);
1071 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1073 if (chatroom != NULL)
1074 found = empathy_chatroom_is_favorite (chatroom);
1076 DEBUG ("This room %s favorite", found ? "is" : "is not");
1077 gtk_toggle_action_set_active (
1078 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1080 if (chatroom != NULL)
1081 found = empathy_chatroom_is_always_urgent (chatroom);
1083 gtk_toggle_action_set_active (
1084 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1087 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1088 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1090 /* Show contacts menu */
1091 g_object_get (priv->current_chat,
1092 "remote-contact", &remote_contact,
1093 "show-contacts", &active,
1095 if (remote_contact == NULL) {
1096 gtk_toggle_action_set_active (
1097 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1100 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1101 (remote_contact == NULL));
1102 if (remote_contact != NULL) {
1103 g_object_unref (remote_contact);
1108 chat_window_clear_activate_cb (GtkAction *action,
1109 EmpathyChatWindow *window)
1111 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1113 empathy_chat_clear (priv->current_chat);
1117 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1118 EmpathyChatWindow *window)
1120 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1125 EmpathyChatroom *chatroom;
1127 active = gtk_toggle_action_get_active (toggle_action);
1128 account = empathy_chat_get_account (priv->current_chat);
1129 room = empathy_chat_get_id (priv->current_chat);
1130 name = empathy_chat_dup_name (priv->current_chat);
1132 chatroom = empathy_chatroom_manager_ensure_chatroom (
1133 priv->chatroom_manager,
1138 empathy_chatroom_set_favorite (chatroom, active);
1139 g_object_unref (chatroom);
1144 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1145 EmpathyChatWindow *window)
1147 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1152 EmpathyChatroom *chatroom;
1154 active = gtk_toggle_action_get_active (toggle_action);
1155 account = empathy_chat_get_account (priv->current_chat);
1156 room = empathy_chat_get_id (priv->current_chat);
1157 name = empathy_chat_dup_name (priv->current_chat);
1159 chatroom = empathy_chatroom_manager_ensure_chatroom (
1160 priv->chatroom_manager,
1165 empathy_chatroom_set_always_urgent (chatroom, active);
1166 g_object_unref (chatroom);
1171 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1172 EmpathyChatWindow *window)
1174 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1177 active = gtk_toggle_action_get_active (toggle_action);
1179 empathy_chat_set_show_contacts (priv->current_chat, active);
1183 chat_window_invite_participant_activate_cb (GtkAction *action,
1184 EmpathyChatWindow *window)
1186 EmpathyChatWindowPriv *priv;
1188 EmpathyTpChat *tp_chat;
1191 priv = GET_PRIV (window);
1193 g_return_if_fail (priv->current_chat != NULL);
1195 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1197 dialog = empathy_invite_participant_dialog_new (
1198 GTK_WINDOW (priv->dialog), tp_chat);
1199 gtk_widget_show (dialog);
1201 response = gtk_dialog_run (GTK_DIALOG (dialog));
1203 if (response == GTK_RESPONSE_ACCEPT) {
1204 TpContact *tp_contact;
1205 EmpathyContact *contact;
1207 tp_contact = empathy_invite_participant_dialog_get_selected (
1208 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1209 if (tp_contact == NULL) goto out;
1211 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1213 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1215 g_object_unref (contact);
1219 gtk_widget_destroy (dialog);
1223 chat_window_close_activate_cb (GtkAction *action,
1224 EmpathyChatWindow *window)
1226 EmpathyChatWindowPriv *priv;
1228 priv = GET_PRIV (window);
1230 g_return_if_fail (priv->current_chat != NULL);
1232 maybe_close_chat (window, priv->current_chat);
1236 chat_window_edit_activate_cb (GtkAction *action,
1237 EmpathyChatWindow *window)
1239 EmpathyChatWindowPriv *priv;
1240 GtkClipboard *clipboard;
1241 GtkTextBuffer *buffer;
1242 gboolean text_available;
1244 priv = GET_PRIV (window);
1246 g_return_if_fail (priv->current_chat != NULL);
1248 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1249 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1250 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1251 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1255 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1256 if (gtk_text_buffer_get_has_selection (buffer)) {
1257 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1258 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1262 selection = empathy_theme_adium_get_has_selection (priv->current_chat->view);
1264 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1265 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1268 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1269 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1270 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1274 chat_window_cut_activate_cb (GtkAction *action,
1275 EmpathyChatWindow *window)
1277 EmpathyChatWindowPriv *priv;
1279 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1281 priv = GET_PRIV (window);
1283 empathy_chat_cut (priv->current_chat);
1287 chat_window_copy_activate_cb (GtkAction *action,
1288 EmpathyChatWindow *window)
1290 EmpathyChatWindowPriv *priv;
1292 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1294 priv = GET_PRIV (window);
1296 empathy_chat_copy (priv->current_chat);
1300 chat_window_paste_activate_cb (GtkAction *action,
1301 EmpathyChatWindow *window)
1303 EmpathyChatWindowPriv *priv;
1305 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1307 priv = GET_PRIV (window);
1309 empathy_chat_paste (priv->current_chat);
1313 chat_window_find_activate_cb (GtkAction *action,
1314 EmpathyChatWindow *window)
1316 EmpathyChatWindowPriv *priv;
1318 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1320 priv = GET_PRIV (window);
1322 empathy_chat_find (priv->current_chat);
1326 chat_window_tabs_next_activate_cb (GtkAction *action,
1327 EmpathyChatWindow *window)
1329 EmpathyChatWindowPriv *priv;
1330 gint index_, numPages;
1331 gboolean wrap_around;
1333 priv = GET_PRIV (window);
1335 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1336 &wrap_around, NULL);
1338 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1339 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1341 if (index_ == (numPages - 1) && wrap_around) {
1342 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1346 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1350 chat_window_tabs_previous_activate_cb (GtkAction *action,
1351 EmpathyChatWindow *window)
1353 EmpathyChatWindowPriv *priv;
1354 gint index_, numPages;
1355 gboolean wrap_around;
1357 priv = GET_PRIV (window);
1359 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1360 &wrap_around, NULL);
1362 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1363 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1365 if (index_ <= 0 && wrap_around) {
1366 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1370 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1374 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1375 EmpathyChatWindow *window)
1377 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1378 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1379 empathy_get_current_action_time ());
1383 chat_window_tabs_left_activate_cb (GtkAction *action,
1384 EmpathyChatWindow *window)
1386 EmpathyChatWindowPriv *priv;
1388 gint index_, num_pages;
1390 priv = GET_PRIV (window);
1392 chat = priv->current_chat;
1393 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1398 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1402 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1403 chat_window_menu_context_update (priv, num_pages);
1407 chat_window_tabs_right_activate_cb (GtkAction *action,
1408 EmpathyChatWindow *window)
1410 EmpathyChatWindowPriv *priv;
1412 gint index_, num_pages;
1414 priv = GET_PRIV (window);
1416 chat = priv->current_chat;
1417 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1419 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1423 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1424 chat_window_menu_context_update (priv, num_pages);
1427 static EmpathyChatWindow *
1428 empathy_chat_window_new (void)
1430 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1434 chat_window_detach_activate_cb (GtkAction *action,
1435 EmpathyChatWindow *window)
1437 EmpathyChatWindowPriv *priv;
1438 EmpathyChatWindow *new_window;
1441 priv = GET_PRIV (window);
1443 chat = priv->current_chat;
1444 new_window = empathy_chat_window_new ();
1446 empathy_chat_window_move_chat (window, new_window, chat);
1448 priv = GET_PRIV (new_window);
1449 gtk_widget_show (priv->dialog);
1453 chat_window_help_contents_activate_cb (GtkAction *action,
1454 EmpathyChatWindow *window)
1456 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1458 empathy_url_show (priv->dialog, "help:empathy");
1462 chat_window_help_about_activate_cb (GtkAction *action,
1463 EmpathyChatWindow *window)
1465 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1467 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1471 chat_window_delete_event_cb (GtkWidget *dialog,
1473 EmpathyChatWindow *window)
1475 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1476 EmpathyChat *chat = NULL;
1480 DEBUG ("Delete event received");
1482 for (l = priv->chats; l != NULL; l = l->next) {
1483 if (chat_needs_close_confirmation (l->data)) {
1490 confirm_close (window, TRUE, n_rooms,
1491 (n_rooms == 1 ? chat : NULL));
1493 remove_all_chats (window);
1500 chat_window_composing_cb (EmpathyChat *chat,
1501 gboolean is_composing,
1502 EmpathyChatWindow *window)
1504 chat_window_update_chat_tab (chat);
1508 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1511 EmpathyChatWindowPriv *priv;
1513 priv = GET_PRIV (window);
1515 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1519 chat_window_notification_closed_cb (NotifyNotification *notify,
1520 EmpathyChatWindow *self)
1522 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1524 g_object_unref (notify);
1525 if (priv->notification == notify) {
1526 priv->notification = NULL;
1531 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1532 EmpathyMessage *message,
1535 EmpathyContact *sender;
1536 const gchar *header;
1540 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1541 gboolean res, has_x_canonical_append;
1542 NotifyNotification *notification = priv->notification;
1544 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1547 res = g_settings_get_boolean (priv->gsettings_notif,
1548 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1555 sender = empathy_message_get_sender (message);
1556 header = empathy_contact_get_alias (sender);
1557 body = empathy_message_get_body (message);
1558 escaped = g_markup_escape_text (body, -1);
1559 has_x_canonical_append = empathy_notify_manager_has_capability (
1560 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1562 if (notification != NULL && !has_x_canonical_append) {
1563 /* if the notification server supports x-canonical-append, it is
1564 better to not use notify_notification_update to avoid
1565 overwriting the current notification message */
1566 notify_notification_update (notification,
1567 header, escaped, NULL);
1569 /* if the notification server supports x-canonical-append,
1570 the hint will be added, so that the message from the
1571 just created notification will be automatically appended
1572 to an existing notification with the same title.
1573 In this way the previous message will not be lost: the new
1574 message will appear below it, in the same notification */
1575 notification = notify_notification_new (header, escaped, NULL);
1577 if (priv->notification == NULL) {
1578 priv->notification = notification;
1581 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1583 tp_g_signal_connect_object (notification, "closed",
1584 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1586 if (has_x_canonical_append) {
1587 /* We have to set a not empty string to keep libnotify happy */
1588 notify_notification_set_hint_string (notification,
1589 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1593 const gchar *category = empathy_chat_is_room (chat)
1594 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1595 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1596 notify_notification_set_hint (notification,
1597 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1598 g_variant_new_string (category));
1602 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1603 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1605 if (pixbuf != NULL) {
1606 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1607 g_object_unref (pixbuf);
1610 notify_notification_show (notification, NULL);
1616 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1618 EmpathyChatWindowPriv *priv;
1621 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1623 priv = GET_PRIV (window);
1625 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1631 chat_window_new_message_cb (EmpathyChat *chat,
1632 EmpathyMessage *message,
1634 gboolean should_highlight,
1635 EmpathyChatWindow *window)
1637 EmpathyChatWindowPriv *priv;
1639 gboolean needs_urgency;
1640 EmpathyContact *sender;
1642 priv = GET_PRIV (window);
1644 has_focus = empathy_chat_window_has_focus (window);
1646 /* - if we're the sender, we play the sound if it's specified in the
1647 * preferences and we're not away.
1648 * - if we receive a message, we play the sound if it's specified in the
1649 * preferences and the window does not have focus on the chat receiving
1653 sender = empathy_message_get_sender (message);
1655 if (empathy_contact_is_user (sender)) {
1656 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1657 EMPATHY_SOUND_MESSAGE_OUTGOING);
1660 if (has_focus && priv->current_chat == chat) {
1661 /* window and tab are focused so consider the message to be read */
1663 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1664 empathy_chat_messages_read (chat);
1668 /* Update the chat tab if this is the first unread message */
1669 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1670 chat_window_update_chat_tab (chat);
1673 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1674 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1675 * an unamed MUC (msn-like).
1676 * In case of a MUC, we set urgency if either:
1677 * a) the chatroom's always_urgent property is TRUE
1678 * b) the message contains our alias
1680 if (empathy_chat_is_room (chat)) {
1683 EmpathyChatroom *chatroom;
1685 account = empathy_chat_get_account (chat);
1686 room = empathy_chat_get_id (chat);
1688 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1691 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1692 needs_urgency = TRUE;
1694 needs_urgency = should_highlight;
1697 needs_urgency = TRUE;
1700 if (needs_urgency) {
1702 chat_window_set_urgency_hint (window, TRUE);
1705 /* Pending messages have already been displayed and notified in the
1706 * approver, so we don't display a notification and play a sound for those */
1708 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1709 EMPATHY_SOUND_MESSAGE_INCOMING);
1711 chat_window_show_or_update_notification (window, message, chat);
1715 /* update the number of unread messages and the window icon */
1716 chat_window_title_update (priv);
1717 chat_window_icon_update (priv, TRUE);
1721 chat_window_command_part (EmpathyChat *chat,
1724 EmpathyChat *chat_to_be_parted;
1725 EmpathyTpChat *tp_chat = NULL;
1727 if (strv[1] == NULL) {
1728 /* No chatroom ID specified */
1729 tp_chat = empathy_chat_get_tp_chat (chat);
1731 empathy_tp_chat_leave (tp_chat, "");
1734 chat_to_be_parted = empathy_chat_window_find_chat (
1735 empathy_chat_get_account (chat), strv[1], FALSE);
1737 if (chat_to_be_parted != NULL) {
1738 /* Found a chatroom matching the specified ID */
1739 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1741 empathy_tp_chat_leave (tp_chat, strv[2]);
1745 /* Going by the syntax of PART command:
1747 * /PART [<chatroom-ID>] [<reason>]
1749 * Chatroom-ID is not a must to specify a reason.
1750 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1751 * MUC then the current chatroom should be parted and srtv[1] should
1752 * be treated as part of the optional part-message. */
1753 message = g_strconcat (strv[1], " ", strv[2], NULL);
1754 tp_chat = empathy_chat_get_tp_chat (chat);
1756 empathy_tp_chat_leave (tp_chat, message);
1762 static GtkNotebook *
1763 notebook_create_window_cb (GtkNotebook *source,
1769 EmpathyChatWindowPriv *priv;
1770 EmpathyChatWindow *window, *new_window;
1773 chat = EMPATHY_CHAT (page);
1774 window = chat_window_find_chat (chat);
1776 new_window = empathy_chat_window_new ();
1777 priv = GET_PRIV (new_window);
1779 DEBUG ("Detach hook called");
1781 empathy_chat_window_move_chat (window, new_window, chat);
1783 gtk_widget_show (priv->dialog);
1784 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1790 chat_window_page_switched_cb (GtkNotebook *notebook,
1793 EmpathyChatWindow *window)
1795 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1796 EmpathyChat *chat = EMPATHY_CHAT (child);
1798 DEBUG ("Page switched");
1800 if (priv->page_added) {
1801 priv->page_added = FALSE;
1802 empathy_chat_scroll_down (chat);
1804 else if (priv->current_chat == chat) {
1808 priv->current_chat = chat;
1809 empathy_chat_messages_read (chat);
1811 chat_window_update_chat_tab (chat);
1815 chat_window_page_added_cb (GtkNotebook *notebook,
1818 EmpathyChatWindow *window)
1820 EmpathyChatWindowPriv *priv;
1823 priv = GET_PRIV (window);
1825 /* If we just received DND to the same window, we don't want
1826 * to do anything here like removing the tab and then readding
1827 * it, so we return here and in "page-added".
1829 if (priv->dnd_same_window) {
1830 DEBUG ("Page added (back to the same window)");
1831 priv->dnd_same_window = FALSE;
1835 DEBUG ("Page added");
1837 /* Get chat object */
1838 chat = EMPATHY_CHAT (child);
1840 /* Connect chat signals for this window */
1841 g_signal_connect (chat, "composing",
1842 G_CALLBACK (chat_window_composing_cb),
1844 g_signal_connect (chat, "new-message",
1845 G_CALLBACK (chat_window_new_message_cb),
1847 g_signal_connect (chat, "part-command-entered",
1848 G_CALLBACK (chat_window_command_part),
1850 g_signal_connect (chat, "notify::tp-chat",
1851 G_CALLBACK (chat_window_update_chat_tab),
1854 /* Set flag so we know to perform some special operations on
1855 * switch page due to the new page being added.
1857 priv->page_added = TRUE;
1859 /* Get list of chats up to date */
1860 priv->chats = g_list_append (priv->chats, chat);
1862 chat_window_update_chat_tab (chat);
1866 chat_window_page_removed_cb (GtkNotebook *notebook,
1869 EmpathyChatWindow *window)
1871 EmpathyChatWindowPriv *priv;
1874 priv = GET_PRIV (window);
1876 /* If we just received DND to the same window, we don't want
1877 * to do anything here like removing the tab and then readding
1878 * it, so we return here and in "page-added".
1880 if (priv->dnd_same_window) {
1881 DEBUG ("Page removed (and will be readded to same window)");
1885 DEBUG ("Page removed");
1887 /* Get chat object */
1888 chat = EMPATHY_CHAT (child);
1890 /* Disconnect all signal handlers for this chat and this window */
1891 g_signal_handlers_disconnect_by_func (chat,
1892 G_CALLBACK (chat_window_composing_cb),
1894 g_signal_handlers_disconnect_by_func (chat,
1895 G_CALLBACK (chat_window_new_message_cb),
1897 g_signal_handlers_disconnect_by_func (chat,
1898 G_CALLBACK (chat_window_update_chat_tab),
1901 /* Keep list of chats up to date */
1902 priv->chats = g_list_remove (priv->chats, chat);
1903 empathy_chat_messages_read (chat);
1905 if (priv->chats == NULL) {
1906 g_object_unref (window);
1908 chat_window_update (window, TRUE);
1913 chat_window_focus_in_event_cb (GtkWidget *widget,
1915 EmpathyChatWindow *window)
1917 EmpathyChatWindowPriv *priv;
1919 priv = GET_PRIV (window);
1921 empathy_chat_messages_read (priv->current_chat);
1923 chat_window_set_urgency_hint (window, FALSE);
1925 /* Update the title, since we now mark all unread messages as read. */
1926 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1932 chat_window_drag_drop (GtkWidget *widget,
1933 GdkDragContext *context,
1937 EmpathyChatWindow *window)
1940 EmpathyChatWindowPriv *priv;
1942 priv = GET_PRIV (window);
1944 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1945 if (target == GDK_NONE)
1946 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1948 if (target != GDK_NONE) {
1949 gtk_drag_get_data (widget, context, target, time_);
1957 chat_window_drag_motion (GtkWidget *widget,
1958 GdkDragContext *context,
1962 EmpathyChatWindow *window)
1965 EmpathyChatWindowPriv *priv;
1967 priv = GET_PRIV (window);
1969 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1970 if (target != GDK_NONE) {
1971 /* This is a file drag. Ensure the contact is online and set the
1972 drag type to COPY. Note that it's possible that the tab will
1973 be switched by GTK+ after a timeout from drag_motion without
1974 getting another drag_motion to disable the drop. You have
1975 to hold your mouse really still.
1977 EmpathyContact *contact;
1979 priv = GET_PRIV (window);
1980 contact = empathy_chat_get_remote_contact (priv->current_chat);
1981 /* contact is NULL for multi-user chats. We don't do
1982 * file transfers to MUCs. We also don't send files
1983 * to offline contacts or contacts that don't support
1986 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1987 gdk_drag_status (context, 0, time_);
1990 if (!(empathy_contact_get_capabilities (contact)
1991 & EMPATHY_CAPABILITIES_FT)) {
1992 gdk_drag_status (context, 0, time_);
1995 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1999 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
2000 if (target != GDK_NONE) {
2001 /* This is a drag of a contact from a contact list. Set to COPY.
2002 FIXME: If this drag is to a MUC window, it invites the user.
2003 Otherwise, it opens a chat. Should we use a different drag
2004 type for invites? Should we allow ASK?
2006 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2014 drag_data_received_individual_id (EmpathyChatWindow *self,
2016 GdkDragContext *context,
2019 GtkSelectionData *selection,
2024 EmpathyIndividualManager *manager = NULL;
2025 FolksIndividual *individual;
2026 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2027 EmpathyTpChat *chat;
2028 TpContact *tp_contact;
2030 EmpathyContact *contact;
2032 id = (const gchar *) gtk_selection_data_get_data (selection);
2034 DEBUG ("DND invididual %s", id);
2036 if (priv->current_chat == NULL)
2039 chat = empathy_chat_get_tp_chat (priv->current_chat);
2043 if (!empathy_tp_chat_can_add_contact (chat)) {
2044 DEBUG ("Can't invite contact to %s",
2045 tp_proxy_get_object_path (chat));
2049 manager = empathy_individual_manager_dup_singleton ();
2051 individual = empathy_individual_manager_lookup_member (manager, id);
2052 if (individual == NULL) {
2053 DEBUG ("Failed to find individual %s", id);
2057 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2058 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2059 if (tp_contact == NULL) {
2060 DEBUG ("Can't find a TpContact on connection %s for %s",
2061 tp_proxy_get_object_path (conn), id);
2065 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2066 tp_channel_get_identifier ((TpChannel *) chat));
2068 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2069 empathy_tp_chat_add (chat, contact, NULL);
2070 g_object_unref (contact);
2073 gtk_drag_finish (context, TRUE, FALSE, time_);
2074 tp_clear_object (&manager);
2078 chat_window_drag_data_received (GtkWidget *widget,
2079 GdkDragContext *context,
2082 GtkSelectionData *selection,
2085 EmpathyChatWindow *window)
2087 if (info == DND_DRAG_TYPE_CONTACT_ID) {
2088 EmpathyChat *chat = NULL;
2089 EmpathyChatWindow *old_window;
2090 TpAccount *account = NULL;
2091 EmpathyClientFactory *factory;
2094 const gchar *account_id;
2095 const gchar *contact_id;
2097 id = (const gchar*) gtk_selection_data_get_data (selection);
2099 factory = empathy_client_factory_dup ();
2101 DEBUG ("DND contact from roster with id:'%s'", id);
2103 strv = g_strsplit (id, ":", 2);
2104 if (g_strv_length (strv) == 2) {
2105 account_id = strv[0];
2106 contact_id = strv[1];
2108 tp_simple_client_factory_ensure_account (
2109 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2112 g_object_unref (factory);
2113 if (account != NULL)
2114 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2117 if (account == NULL) {
2119 gtk_drag_finish (context, FALSE, FALSE, time_);
2124 empathy_chat_with_contact_id (
2125 account, contact_id,
2126 empathy_get_current_action_time (),
2134 old_window = chat_window_find_chat (chat);
2136 if (old_window == window) {
2137 gtk_drag_finish (context, TRUE, FALSE, time_);
2141 empathy_chat_window_move_chat (old_window, window, chat);
2143 empathy_chat_window_add_chat (window, chat);
2146 /* Added to take care of any outstanding chat events */
2147 empathy_chat_window_present_chat (chat,
2148 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2150 /* We should return TRUE to remove the data when doing
2151 * GDK_ACTION_MOVE, but we don't here otherwise it has
2152 * weird consequences, and we handle that internally
2153 * anyway with add_chat () and remove_chat ().
2155 gtk_drag_finish (context, TRUE, FALSE, time_);
2157 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2158 drag_data_received_individual_id (window, widget, context, x, y,
2159 selection, info, time_);
2161 else if (info == DND_DRAG_TYPE_URI_LIST) {
2162 EmpathyChatWindowPriv *priv;
2163 EmpathyContact *contact;
2166 priv = GET_PRIV (window);
2167 contact = empathy_chat_get_remote_contact (priv->current_chat);
2169 /* contact is NULL when current_chat is a multi-user chat.
2170 * We don't do file transfers to MUCs, so just cancel the drag.
2172 if (contact == NULL) {
2173 gtk_drag_finish (context, TRUE, FALSE, time_);
2177 data = (const gchar *) gtk_selection_data_get_data (selection);
2178 empathy_send_file_from_uri_list (contact, data);
2180 gtk_drag_finish (context, TRUE, FALSE, time_);
2182 else if (info == DND_DRAG_TYPE_TAB) {
2184 EmpathyChatWindow *old_window = NULL;
2188 chat = (void *) gtk_selection_data_get_data (selection);
2189 old_window = chat_window_find_chat (*chat);
2192 EmpathyChatWindowPriv *priv;
2194 priv = GET_PRIV (window);
2195 priv->dnd_same_window = (old_window == window);
2196 DEBUG ("DND tab (within same window: %s)",
2197 priv->dnd_same_window ? "Yes" : "No");
2200 DEBUG ("DND from unknown source");
2201 gtk_drag_finish (context, FALSE, FALSE, time_);
2206 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2207 guint num_chats_in_manager,
2208 EmpathyChatWindow *window)
2210 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2212 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2213 num_chats_in_manager > 0);
2217 chat_window_finalize (GObject *object)
2219 EmpathyChatWindow *window;
2220 EmpathyChatWindowPriv *priv;
2222 window = EMPATHY_CHAT_WINDOW (object);
2223 priv = GET_PRIV (window);
2225 DEBUG ("Finalized: %p", object);
2227 g_object_unref (priv->ui_manager);
2228 g_object_unref (priv->chatroom_manager);
2229 g_object_unref (priv->notify_mgr);
2230 g_object_unref (priv->gsettings_chat);
2231 g_object_unref (priv->gsettings_notif);
2232 g_object_unref (priv->gsettings_ui);
2233 g_object_unref (priv->sound_mgr);
2235 if (priv->notification != NULL) {
2236 notify_notification_close (priv->notification, NULL);
2237 priv->notification = NULL;
2240 if (priv->contact_targets) {
2241 gtk_target_list_unref (priv->contact_targets);
2243 if (priv->file_targets) {
2244 gtk_target_list_unref (priv->file_targets);
2247 if (priv->chat_manager) {
2248 g_signal_handler_disconnect (priv->chat_manager,
2249 priv->chat_manager_chats_changed_id);
2250 g_object_unref (priv->chat_manager);
2251 priv->chat_manager = NULL;
2254 chat_windows = g_list_remove (chat_windows, window);
2255 gtk_widget_destroy (priv->dialog);
2257 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2261 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2263 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2265 object_class->finalize = chat_window_finalize;
2267 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2271 empathy_chat_window_init (EmpathyChatWindow *window)
2274 GtkAccelGroup *accel_group;
2279 GtkWidget *chat_vbox;
2281 EmpathySmileyManager *smiley_manager;
2282 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2283 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2285 window->priv = priv;
2286 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2287 gui = empathy_builder_get_file (filename,
2288 "chat_window", &priv->dialog,
2289 "chat_vbox", &chat_vbox,
2290 "ui_manager", &priv->ui_manager,
2291 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2292 "menu_conv_favorite", &priv->menu_conv_favorite,
2293 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2294 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2295 "menu_edit_cut", &priv->menu_edit_cut,
2296 "menu_edit_copy", &priv->menu_edit_copy,
2297 "menu_edit_paste", &priv->menu_edit_paste,
2298 "menu_edit_find", &priv->menu_edit_find,
2299 "menu_tabs_next", &priv->menu_tabs_next,
2300 "menu_tabs_prev", &priv->menu_tabs_prev,
2301 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2302 "menu_tabs_left", &priv->menu_tabs_left,
2303 "menu_tabs_right", &priv->menu_tabs_right,
2304 "menu_tabs_detach", &priv->menu_tabs_detach,
2308 empathy_builder_connect (gui, window,
2309 "menu_conv", "activate", chat_window_conv_activate_cb,
2310 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2311 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2312 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2313 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2314 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2315 "menu_conv_close", "activate", chat_window_close_activate_cb,
2316 "menu_edit", "activate", chat_window_edit_activate_cb,
2317 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2318 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2319 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2320 "menu_edit_find", "activate", chat_window_find_activate_cb,
2321 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2322 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2323 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2324 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2325 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2326 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2327 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2328 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2331 g_object_ref (priv->ui_manager);
2332 g_object_unref (gui);
2334 empathy_set_css_provider (GTK_WIDGET (priv->dialog));
2336 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2337 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2338 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2339 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2341 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2343 priv->notebook = gtk_notebook_new ();
2345 g_signal_connect (priv->notebook, "create-window",
2346 G_CALLBACK (notebook_create_window_cb), window);
2348 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2349 "EmpathyChatWindow");
2350 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2351 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2352 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2353 gtk_widget_show (priv->notebook);
2356 accel_group = gtk_accel_group_new ();
2357 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2359 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2360 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2363 gtk_accel_group_connect (accel_group,
2370 g_object_unref (accel_group);
2372 /* Set up drag target lists */
2373 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2374 G_N_ELEMENTS (drag_types_dest_contact));
2375 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2376 G_N_ELEMENTS (drag_types_dest_file));
2378 /* Set up smiley menu */
2379 smiley_manager = empathy_smiley_manager_dup_singleton ();
2380 submenu = empathy_smiley_menu_new (smiley_manager,
2381 chat_window_insert_smiley_activate_cb,
2383 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2384 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2385 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2386 g_object_unref (smiley_manager);
2388 /* Set up signals we can't do with ui file since we may need to
2389 * block/unblock them at some later stage.
2392 g_signal_connect (priv->dialog,
2394 G_CALLBACK (chat_window_delete_event_cb),
2396 g_signal_connect (priv->dialog,
2398 G_CALLBACK (chat_window_focus_in_event_cb),
2400 g_signal_connect_after (priv->notebook,
2402 G_CALLBACK (chat_window_page_switched_cb),
2404 g_signal_connect (priv->notebook,
2406 G_CALLBACK (chat_window_page_added_cb),
2408 g_signal_connect (priv->notebook,
2410 G_CALLBACK (chat_window_page_removed_cb),
2413 /* Set up drag and drop */
2414 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2415 GTK_DEST_DEFAULT_HIGHLIGHT,
2417 G_N_ELEMENTS (drag_types_dest),
2418 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2420 /* connect_after to allow GtkNotebook's built-in tab switching */
2421 g_signal_connect_after (priv->notebook,
2423 G_CALLBACK (chat_window_drag_motion),
2425 g_signal_connect (priv->notebook,
2426 "drag-data-received",
2427 G_CALLBACK (chat_window_drag_data_received),
2429 g_signal_connect (priv->notebook,
2431 G_CALLBACK (chat_window_drag_drop),
2434 chat_windows = g_list_prepend (chat_windows, window);
2436 /* Set up private details */
2438 priv->current_chat = NULL;
2439 priv->notification = NULL;
2441 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2443 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2444 priv->chat_manager_chats_changed_id =
2445 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2446 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2449 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2450 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2454 /* Returns the window to open a new tab in if there is a suitable window,
2455 * otherwise, returns NULL indicating that a new window should be added.
2457 static EmpathyChatWindow *
2458 empathy_chat_window_get_default (gboolean room)
2460 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2462 gboolean separate_windows = TRUE;
2464 separate_windows = g_settings_get_boolean (gsettings,
2465 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2467 g_object_unref (gsettings);
2469 if (separate_windows) {
2470 /* Always create a new window */
2474 for (l = chat_windows; l; l = l->next) {
2475 EmpathyChatWindow *chat_window;
2476 guint nb_rooms, nb_private;
2478 chat_window = l->data;
2480 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2482 /* Skip the window if there aren't any rooms in it */
2483 if (room && nb_rooms == 0)
2486 /* Skip the window if there aren't any 1-1 chats in it */
2487 if (!room && nb_private == 0)
2497 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2500 EmpathyChatWindowPriv *priv;
2502 GtkWidget *popup_label;
2504 GValue value = { 0, };
2506 g_return_if_fail (window != NULL);
2507 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2509 priv = GET_PRIV (window);
2511 /* Reference the chat object */
2512 g_object_ref (chat);
2514 /* If this window has just been created, position it */
2515 if (priv->chats == NULL) {
2516 const gchar *name = "chat-window";
2517 gboolean separate_windows;
2519 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2520 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2522 if (empathy_chat_is_room (chat))
2523 name = "room-window";
2525 if (separate_windows) {
2528 /* Save current position of the window */
2529 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2531 /* First bind to the 'generic' name. So new window for which we didn't
2532 * save a geometry yet will have the geometry of the last saved
2533 * window (bgo #601191). */
2534 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2536 /* Restore previous position of the window so the newly created window
2537 * won't be in the same position as the latest saved window and so
2538 * completely hide it. */
2539 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2541 /* Then bind it to the name of the contact/room so we'll save the
2542 * geometry specific to this window */
2543 name = empathy_chat_get_id (chat);
2546 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2549 child = GTK_WIDGET (chat);
2550 label = chat_window_create_label (window, chat, TRUE);
2551 popup_label = chat_window_create_label (window, chat, FALSE);
2552 gtk_widget_show (child);
2554 g_signal_connect (chat, "notify::name",
2555 G_CALLBACK (chat_window_chat_notify_cb),
2557 g_signal_connect (chat, "notify::subject",
2558 G_CALLBACK (chat_window_chat_notify_cb),
2560 g_signal_connect (chat, "notify::remote-contact",
2561 G_CALLBACK (chat_window_chat_notify_cb),
2563 g_signal_connect (chat, "notify::sms-channel",
2564 G_CALLBACK (chat_window_chat_notify_cb),
2566 g_signal_connect (chat, "notify::n-messages-sending",
2567 G_CALLBACK (chat_window_chat_notify_cb),
2569 g_signal_connect (chat, "notify::nb-unread-messages",
2570 G_CALLBACK (chat_window_chat_notify_cb),
2572 chat_window_chat_notify_cb (chat);
2574 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2575 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2576 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2577 g_value_init (&value, G_TYPE_BOOLEAN);
2578 g_value_set_boolean (&value, TRUE);
2579 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2580 child, "tab-expand" , &value);
2581 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2582 child, "tab-fill" , &value);
2583 g_value_unset (&value);
2585 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2589 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2592 EmpathyChatWindowPriv *priv;
2594 EmpathyContact *remote_contact;
2595 EmpathyChatManager *chat_manager;
2597 g_return_if_fail (window != NULL);
2598 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2600 priv = GET_PRIV (window);
2602 g_signal_handlers_disconnect_by_func (chat,
2603 chat_window_chat_notify_cb,
2605 remote_contact = g_object_get_data (G_OBJECT (chat),
2606 "chat-window-remote-contact");
2607 if (remote_contact) {
2608 g_signal_handlers_disconnect_by_func (remote_contact,
2609 chat_window_update_chat_tab,
2613 chat_manager = empathy_chat_manager_dup_singleton ();
2614 empathy_chat_manager_closed_chat (chat_manager, chat);
2615 g_object_unref (chat_manager);
2617 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2619 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2621 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2623 g_object_unref (chat);
2627 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2628 EmpathyChatWindow *new_window,
2633 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2634 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2635 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2637 widget = GTK_WIDGET (chat);
2639 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2640 G_OBJECT (widget)->ref_count);
2642 /* We reference here to make sure we don't loose the widget
2643 * and the EmpathyChat object during the move.
2645 g_object_ref (chat);
2646 g_object_ref (widget);
2648 empathy_chat_window_remove_chat (old_window, chat);
2649 empathy_chat_window_add_chat (new_window, chat);
2651 g_object_unref (widget);
2652 g_object_unref (chat);
2656 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2659 EmpathyChatWindowPriv *priv;
2662 g_return_if_fail (window != NULL);
2663 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2665 priv = GET_PRIV (window);
2667 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2669 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2674 empathy_chat_window_find_chat (TpAccount *account,
2676 gboolean sms_channel)
2680 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2682 for (l = chat_windows; l; l = l->next) {
2683 EmpathyChatWindowPriv *priv;
2684 EmpathyChatWindow *window;
2688 priv = GET_PRIV (window);
2690 for (ll = priv->chats; ll; ll = ll->next) {
2695 if (account == empathy_chat_get_account (chat) &&
2696 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2697 sms_channel == empathy_chat_is_sms_channel (chat)) {
2707 empathy_chat_window_present_chat (EmpathyChat *chat,
2710 EmpathyChatWindow *window;
2711 EmpathyChatWindowPriv *priv;
2712 guint32 x_timestamp;
2714 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2716 window = chat_window_find_chat (chat);
2718 /* If the chat has no window, create one */
2719 if (window == NULL) {
2720 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2722 window = empathy_chat_window_new ();
2724 /* we want to display the newly created window even if we don't present
2726 priv = GET_PRIV (window);
2727 gtk_widget_show (priv->dialog);
2730 empathy_chat_window_add_chat (window, chat);
2733 /* Don't force the window to show itself when it wasn't
2734 * an action by the user
2736 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2739 priv = GET_PRIV (window);
2741 if (x_timestamp != GDK_CURRENT_TIME) {
2742 /* Don't present or switch tab if the action was earlier than the
2743 * last actions X time, accounting for overflow and the first ever
2746 if (priv->x_user_action_time != 0
2747 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2750 priv->x_user_action_time = x_timestamp;
2753 empathy_chat_window_switch_to_chat (window, chat);
2755 /* Don't use empathy_window_present_with_time () which would move the window
2756 * to our current desktop but move to the window's desktop instead. This is
2757 * more coherent with Shell's 'app is ready' notication which moves the view
2758 * to the app desktop rather than moving the app itself. */
2759 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2761 gtk_widget_grab_focus (chat->input_text_view);
2765 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2769 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2771 guint _nb_rooms = 0, _nb_private = 0;
2773 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2774 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2780 if (nb_rooms != NULL)
2781 *nb_rooms = _nb_rooms;
2782 if (nb_private != NULL)
2783 *nb_private = _nb_private;