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 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
140 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] = {
144 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
145 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
148 static const GtkTargetEntry drag_types_dest_file[] = {
149 /* must be first to be prioritized, in order to receive the
150 * note's file path from Tomboy instead of an URI */
151 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
152 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
155 static void chat_window_update (EmpathyChatWindow *window,
156 gboolean update_contact_menu);
158 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
161 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
164 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
165 EmpathyChatWindow *new_window,
168 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
172 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
175 chat_window_accel_cb (GtkAccelGroup *accelgroup,
179 EmpathyChatWindow *window)
181 EmpathyChatWindowPriv *priv;
185 priv = GET_PRIV (window);
187 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
188 if (tab_accel_keys[i] == key) {
195 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
199 static EmpathyChatWindow *
200 chat_window_find_chat (EmpathyChat *chat)
202 EmpathyChatWindowPriv *priv;
205 for (l = chat_windows; l; l = l->next) {
206 priv = GET_PRIV (l->data);
207 ll = g_list_find (priv->chats, chat);
217 remove_all_chats (EmpathyChatWindow *window)
219 EmpathyChatWindowPriv *priv;
221 priv = GET_PRIV (window);
222 g_object_ref (window);
224 while (priv->chats) {
225 empathy_chat_window_remove_chat (window, priv->chats->data);
228 g_object_unref (window);
232 confirm_close_response_cb (GtkWidget *dialog,
234 EmpathyChatWindow *window)
238 chat = g_object_get_data (G_OBJECT (dialog), "chat");
240 gtk_widget_destroy (dialog);
242 if (response != GTK_RESPONSE_ACCEPT)
246 empathy_chat_window_remove_chat (window, chat);
248 remove_all_chats (window);
253 confirm_close (EmpathyChatWindow *window,
254 gboolean close_window,
258 EmpathyChatWindowPriv *priv;
260 gchar *primary, *secondary;
262 g_return_if_fail (n_rooms > 0);
265 g_return_if_fail (chat == NULL);
267 g_return_if_fail (chat != NULL);
270 priv = GET_PRIV (window);
272 /* If there are no chats in this window, how could we possibly have got
275 g_return_if_fail (priv->chats != NULL);
277 /* Treat closing a window which only has one tab exactly like closing
280 if (close_window && priv->chats->next == NULL) {
281 close_window = FALSE;
282 chat = priv->chats->data;
286 primary = g_strdup (_("Close this window?"));
289 gchar *chat_name = empathy_chat_dup_name (chat);
290 secondary = g_strdup_printf (
291 _("Closing this window will leave %s. You will "
292 "not receive any further messages until you "
297 secondary = g_strdup_printf (
298 /* Note to translators: the number of chats will
299 * always be at least 2.
302 "Closing this window will leave a chat room. You will "
303 "not receive any further messages until you rejoin it.",
304 "Closing this window will leave %u chat rooms. You will "
305 "not receive any further messages until you rejoin them.",
310 gchar *chat_name = empathy_chat_dup_name (chat);
311 primary = g_strdup_printf (_("Leave %s?"), chat_name);
312 secondary = g_strdup (_("You will not receive any further messages from this chat "
313 "room until you rejoin it."));
317 dialog = gtk_message_dialog_new (
318 GTK_WINDOW (priv->dialog),
319 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
324 gtk_window_set_title (GTK_WINDOW (dialog), "");
325 g_object_set (dialog, "secondary-text", secondary, NULL);
330 gtk_dialog_add_button (GTK_DIALOG (dialog),
331 close_window ? _("Close window") : _("Leave room"),
332 GTK_RESPONSE_ACCEPT);
333 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
334 GTK_RESPONSE_ACCEPT);
337 g_object_set_data (G_OBJECT (dialog), "chat", chat);
340 g_signal_connect (dialog, "response",
341 G_CALLBACK (confirm_close_response_cb), window);
343 gtk_window_present (GTK_WINDOW (dialog));
346 /* Returns TRUE if we should check if the user really wants to leave. If it's
347 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
348 * the user is actually in the room as opposed to having been kicked or gone
349 * offline or something), then we should check.
352 chat_needs_close_confirmation (EmpathyChat *chat)
354 return (empathy_chat_is_room (chat)
355 && empathy_chat_get_tp_chat (chat) != NULL);
359 maybe_close_chat (EmpathyChatWindow *window,
362 g_return_if_fail (chat != NULL);
364 if (chat_needs_close_confirmation (chat)) {
365 confirm_close (window, FALSE, 1, chat);
367 empathy_chat_window_remove_chat (window, chat);
372 chat_window_close_clicked_cb (GtkAction *action,
375 EmpathyChatWindow *window;
377 window = chat_window_find_chat (chat);
378 maybe_close_chat (window, chat);
382 chat_tab_style_updated_cb (GtkWidget *hbox,
386 int char_width, h, w;
387 PangoContext *context;
388 const PangoFontDescription *font_desc;
389 PangoFontMetrics *metrics;
391 button = g_object_get_data (G_OBJECT (user_data),
392 "chat-window-tab-close-button");
393 context = gtk_widget_get_pango_context (hbox);
395 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
396 GTK_STATE_FLAG_NORMAL);
398 metrics = pango_context_get_metrics (context, font_desc,
399 pango_context_get_language (context));
400 char_width = pango_font_metrics_get_approximate_char_width (metrics);
401 pango_font_metrics_unref (metrics);
403 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
404 GTK_ICON_SIZE_MENU, &w, &h);
406 /* Request at least about 12 chars width plus at least space for the status
407 * image and the close button */
408 gtk_widget_set_size_request (hbox,
409 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
411 gtk_widget_set_size_request (button, w, h);
415 create_close_button (void)
417 GtkWidget *button, *image;
419 button = gtk_button_new ();
421 gtk_widget_set_name (button, "empathy-tab-close-button");
423 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
424 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
426 /* We don't want focus/keynav for the button to avoid clutter, and
427 * Ctrl-W works anyway.
429 gtk_widget_set_can_focus (button, FALSE);
430 gtk_widget_set_can_default (button, FALSE);
432 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
434 gtk_widget_show (image);
436 gtk_container_add (GTK_CONTAINER (button), image);
442 chat_window_create_label (EmpathyChatWindow *window,
444 gboolean is_tab_label)
447 GtkWidget *name_label;
448 GtkWidget *status_image;
449 GtkWidget *event_box;
450 GtkWidget *event_box_hbox;
451 PangoAttrList *attr_list;
452 PangoAttribute *attr;
454 /* The spacing between the button and the label. */
455 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
457 event_box = gtk_event_box_new ();
458 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
460 name_label = gtk_label_new (NULL);
462 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
464 attr_list = pango_attr_list_new ();
465 attr = pango_attr_scale_new (1/1.2);
466 attr->start_index = 0;
467 attr->end_index = -1;
468 pango_attr_list_insert (attr_list, attr);
469 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
470 pango_attr_list_unref (attr_list);
472 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
473 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
474 g_object_set_data (G_OBJECT (chat),
475 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
478 status_image = gtk_image_new ();
480 /* Spacing between the icon and label. */
481 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
483 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
484 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
486 g_object_set_data (G_OBJECT (chat),
487 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
489 g_object_set_data (G_OBJECT (chat),
490 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
493 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
494 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
497 GtkWidget *close_button;
498 GtkWidget *sending_spinner;
500 sending_spinner = gtk_spinner_new ();
502 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
504 g_object_set_data (G_OBJECT (chat),
505 "chat-window-tab-sending-spinner",
508 close_button = create_close_button ();
509 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
511 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
513 g_signal_connect (close_button,
515 G_CALLBACK (chat_window_close_clicked_cb),
518 /* React to theme changes and also setup the size correctly. */
519 g_signal_connect (hbox,
521 G_CALLBACK (chat_tab_style_updated_cb),
525 gtk_widget_show_all (hbox);
531 _submenu_notify_visible_changed_cb (GObject *object,
535 g_signal_handlers_disconnect_by_func (object,
536 _submenu_notify_visible_changed_cb,
538 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
542 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
547 gboolean wrap_around;
548 gboolean is_connected;
551 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
552 first_page = (page_num == 0);
553 last_page = (page_num == (num_pages - 1));
554 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
556 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
558 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
560 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
562 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
563 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
564 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
565 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
569 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
570 EmpathyChatWindow *self)
572 EmpathyTpChat *tp_chat;
573 TpConnection *connection;
575 gboolean sensitive = FALSE;
577 g_return_if_fail (priv->current_chat != NULL);
579 action = gtk_ui_manager_get_action (priv->ui_manager,
580 "/chats_menubar/menu_conv/menu_conv_invite_participant");
581 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
583 if (tp_chat != NULL) {
584 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
586 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
587 (tp_connection_get_status (connection, NULL) ==
588 TP_CONNECTION_STATUS_CONNECTED);
591 gtk_action_set_sensitive (action, sensitive);
595 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
596 EmpathyChatWindow *window)
598 GtkWidget *menu, *submenu, *orig_submenu;
600 menu = gtk_ui_manager_get_widget (priv->ui_manager,
601 "/chats_menubar/menu_contact");
602 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
604 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
605 submenu = empathy_chat_get_contact_menu (priv->current_chat);
607 if (submenu != NULL) {
608 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
609 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
611 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
612 gtk_widget_show (menu);
613 gtk_widget_set_sensitive (menu, TRUE);
615 gtk_widget_set_sensitive (menu, FALSE);
618 tp_g_signal_connect_object (orig_submenu,
620 (GCallback)_submenu_notify_visible_changed_cb,
626 get_all_unread_messages (EmpathyChatWindowPriv *priv)
631 for (l = priv->chats; l != NULL; l = g_list_next (l))
632 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
638 get_window_title_name (EmpathyChatWindowPriv *priv)
640 gchar *active_name, *ret;
642 guint current_unread_msgs;
644 nb_chats = g_list_length (priv->chats);
645 g_assert (nb_chats > 0);
647 active_name = empathy_chat_dup_name (priv->current_chat);
649 current_unread_msgs = empathy_chat_get_nb_unread_messages (
654 if (current_unread_msgs == 0)
655 ret = g_strdup (active_name);
657 ret = g_strdup_printf (ngettext (
659 "%s (%d unread)", current_unread_msgs),
660 active_name, current_unread_msgs);
662 guint nb_others = nb_chats - 1;
663 guint all_unread_msgs;
665 all_unread_msgs = get_all_unread_messages (priv);
667 if (all_unread_msgs == 0) {
668 /* no unread message */
669 ret = g_strdup_printf (ngettext (
671 "%s (and %u others)", nb_others),
672 active_name, nb_others);
675 else if (all_unread_msgs == current_unread_msgs) {
676 /* unread messages are in the current tab */
677 ret = g_strdup_printf (ngettext (
679 "%s (%d unread)", current_unread_msgs),
680 active_name, current_unread_msgs);
683 else if (current_unread_msgs == 0) {
684 /* unread messages are in other tabs */
685 ret = g_strdup_printf (ngettext (
686 "%s (%d unread from others)",
687 "%s (%d unread from others)",
689 active_name, all_unread_msgs);
693 /* unread messages are in all the tabs */
694 ret = g_strdup_printf (ngettext (
695 "%s (%d unread from all)",
696 "%s (%d unread from all)",
698 active_name, all_unread_msgs);
702 g_free (active_name);
708 chat_window_title_update (EmpathyChatWindowPriv *priv)
712 name = get_window_title_name (priv);
713 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
718 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
721 EmpathyContact *remote_contact;
722 gboolean avatar_in_icon;
725 n_chats = g_list_length (priv->chats);
727 /* Update window icon */
729 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
730 EMPATHY_IMAGE_MESSAGE);
732 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
733 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
735 if (n_chats == 1 && avatar_in_icon) {
736 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
737 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
738 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
741 g_object_unref (icon);
744 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
750 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
754 GtkWidget *chat_close_button;
757 if (num_pages == 1) {
758 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
759 chat_close_button = g_object_get_data (G_OBJECT (chat),
760 "chat-window-tab-close-button");
761 gtk_widget_hide (chat_close_button);
763 for (i=0; i<num_pages; i++) {
764 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
765 chat_close_button = g_object_get_data (G_OBJECT (chat),
766 "chat-window-tab-close-button");
767 gtk_widget_show (chat_close_button);
773 chat_window_update (EmpathyChatWindow *window,
774 gboolean update_contact_menu)
776 EmpathyChatWindowPriv *priv = GET_PRIV (window);
779 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
781 /* Update Tab menu */
782 chat_window_menu_context_update (priv,
785 chat_window_conversation_menu_update (priv, window);
787 /* If this update is due to a focus-in event, we know the menu will be
788 the same as when we last left it, so no work to do. Besides, if we
789 swap out the menu on a focus-in, we may confuse any external global
791 if (update_contact_menu) {
792 chat_window_contact_menu_update (priv,
796 chat_window_title_update (priv);
798 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
800 chat_window_close_button_update (priv,
805 append_markup_printf (GString *string,
812 va_start (args, format);
814 tmp = g_markup_vprintf_escaped (format, args);
815 g_string_append (string, tmp);
822 chat_window_update_chat_tab_full (EmpathyChat *chat,
823 gboolean update_contact_menu)
825 EmpathyChatWindow *window;
826 EmpathyChatWindowPriv *priv;
827 EmpathyContact *remote_contact;
831 const gchar *subject;
832 const gchar *status = NULL;
836 const gchar *icon_name;
837 GtkWidget *tab_image;
838 GtkWidget *menu_image;
839 GtkWidget *sending_spinner;
842 window = chat_window_find_chat (chat);
846 priv = GET_PRIV (window);
848 /* Get information */
849 name = empathy_chat_dup_name (chat);
850 account = empathy_chat_get_account (chat);
851 subject = empathy_chat_get_subject (chat);
852 remote_contact = empathy_chat_get_remote_contact (chat);
854 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
855 name, tp_proxy_get_object_path (account), subject, remote_contact);
857 /* Update tab image */
858 if (empathy_chat_get_tp_chat (chat) == NULL) {
859 /* No TpChat, we are disconnected */
862 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
863 icon_name = EMPATHY_IMAGE_MESSAGE;
865 else if (remote_contact && empathy_chat_is_composing (chat)) {
866 icon_name = EMPATHY_IMAGE_TYPING;
868 else if (empathy_chat_is_sms_channel (chat)) {
869 icon_name = EMPATHY_IMAGE_SMS;
871 else if (remote_contact) {
872 icon_name = empathy_icon_name_for_contact (remote_contact);
874 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
877 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
878 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
879 if (icon_name != NULL) {
880 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
881 gtk_widget_show (tab_image);
882 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
883 gtk_widget_show (menu_image);
885 gtk_widget_hide (tab_image);
886 gtk_widget_hide (menu_image);
889 /* Update the sending spinner */
890 nb_sending = empathy_chat_get_n_messages_sending (chat);
891 sending_spinner = g_object_get_data (G_OBJECT (chat),
892 "chat-window-tab-sending-spinner");
894 g_object_set (sending_spinner,
895 "active", nb_sending > 0,
896 "visible", nb_sending > 0,
899 /* Update tab tooltip */
900 tooltip = g_string_new (NULL);
902 if (remote_contact) {
903 id = empathy_contact_get_id (remote_contact);
904 status = empathy_contact_get_presence_message (remote_contact);
909 if (empathy_chat_is_sms_channel (chat)) {
910 append_markup_printf (tooltip, "%s ", _("SMS:"));
913 append_markup_printf (tooltip,
914 "<b>%s</b><small> (%s)</small>",
916 tp_account_get_display_name (account));
918 if (nb_sending > 0) {
919 char *tmp = g_strdup_printf (
920 ngettext ("Sending %d message",
921 "Sending %d messages",
925 g_string_append (tooltip, "\n");
926 g_string_append (tooltip, tmp);
928 gtk_widget_set_tooltip_text (sending_spinner, tmp);
932 if (!EMP_STR_EMPTY (status)) {
933 append_markup_printf (tooltip, "\n<i>%s</i>", status);
937 append_markup_printf (tooltip, "\n<b>%s</b> %s",
938 _("Topic:"), subject);
941 if (remote_contact && empathy_chat_is_composing (chat)) {
942 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
945 if (remote_contact != NULL) {
946 const gchar * const *types;
948 types = empathy_contact_get_client_types (remote_contact);
949 if (types != NULL && !tp_strdiff (types[0], "phone")) {
950 /* I'm on a phone ! */
953 name = g_strdup_printf ("☎ %s", name);
958 markup = g_string_free (tooltip, FALSE);
959 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
960 gtk_widget_set_tooltip_markup (widget, markup);
961 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
962 gtk_widget_set_tooltip_markup (widget, markup);
965 /* Update tab and menu label */
966 if (empathy_chat_is_highlighted (chat)) {
967 markup = g_markup_printf_escaped (
968 "<span color=\"red\" weight=\"bold\">%s</span>",
971 markup = g_markup_escape_text (name, -1);
974 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
975 gtk_label_set_markup (GTK_LABEL (widget), markup);
976 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
977 gtk_label_set_markup (GTK_LABEL (widget), markup);
980 /* Update the window if it's the current chat */
981 if (priv->current_chat == chat) {
982 chat_window_update (window, update_contact_menu);
989 chat_window_update_chat_tab (EmpathyChat *chat)
991 chat_window_update_chat_tab_full (chat, TRUE);
995 chat_window_chat_notify_cb (EmpathyChat *chat)
997 EmpathyChatWindow *window;
998 EmpathyContact *old_remote_contact;
999 EmpathyContact *remote_contact = NULL;
1001 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
1002 remote_contact = empathy_chat_get_remote_contact (chat);
1004 if (old_remote_contact != remote_contact) {
1005 /* The remote-contact associated with the chat changed, we need
1006 * to keep track of any change of that contact and update the
1007 * window each time. */
1008 if (remote_contact) {
1009 g_signal_connect_swapped (remote_contact, "notify",
1010 G_CALLBACK (chat_window_update_chat_tab),
1013 if (old_remote_contact) {
1014 g_signal_handlers_disconnect_by_func (old_remote_contact,
1015 chat_window_update_chat_tab,
1019 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1020 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1023 chat_window_update_chat_tab (chat);
1025 window = chat_window_find_chat (chat);
1026 if (window != NULL) {
1027 chat_window_update (window, FALSE);
1032 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1033 EmpathySmiley *smiley,
1036 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1038 GtkTextBuffer *buffer;
1041 chat = priv->current_chat;
1043 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1044 gtk_text_buffer_get_end_iter (buffer, &iter);
1045 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1049 chat_window_conv_activate_cb (GtkAction *action,
1050 EmpathyChatWindow *window)
1052 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1055 EmpathyContact *remote_contact = NULL;
1057 /* Favorite room menu */
1058 is_room = empathy_chat_is_room (priv->current_chat);
1062 gboolean found = FALSE;
1063 EmpathyChatroom *chatroom;
1065 room = empathy_chat_get_id (priv->current_chat);
1066 account = empathy_chat_get_account (priv->current_chat);
1067 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1069 if (chatroom != NULL)
1070 found = empathy_chatroom_is_favorite (chatroom);
1072 DEBUG ("This room %s favorite", found ? "is" : "is not");
1073 gtk_toggle_action_set_active (
1074 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1076 if (chatroom != NULL)
1077 found = empathy_chatroom_is_always_urgent (chatroom);
1079 gtk_toggle_action_set_active (
1080 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1083 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1084 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1086 /* Show contacts menu */
1087 g_object_get (priv->current_chat,
1088 "remote-contact", &remote_contact,
1089 "show-contacts", &active,
1091 if (remote_contact == NULL) {
1092 gtk_toggle_action_set_active (
1093 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1096 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1097 (remote_contact == NULL));
1098 if (remote_contact != NULL) {
1099 g_object_unref (remote_contact);
1104 chat_window_clear_activate_cb (GtkAction *action,
1105 EmpathyChatWindow *window)
1107 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1109 empathy_chat_clear (priv->current_chat);
1113 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1114 EmpathyChatWindow *window)
1116 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1121 EmpathyChatroom *chatroom;
1123 active = gtk_toggle_action_get_active (toggle_action);
1124 account = empathy_chat_get_account (priv->current_chat);
1125 room = empathy_chat_get_id (priv->current_chat);
1126 name = empathy_chat_dup_name (priv->current_chat);
1128 chatroom = empathy_chatroom_manager_ensure_chatroom (
1129 priv->chatroom_manager,
1134 empathy_chatroom_set_favorite (chatroom, active);
1135 g_object_unref (chatroom);
1140 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1141 EmpathyChatWindow *window)
1143 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1148 EmpathyChatroom *chatroom;
1150 active = gtk_toggle_action_get_active (toggle_action);
1151 account = empathy_chat_get_account (priv->current_chat);
1152 room = empathy_chat_get_id (priv->current_chat);
1153 name = empathy_chat_dup_name (priv->current_chat);
1155 chatroom = empathy_chatroom_manager_ensure_chatroom (
1156 priv->chatroom_manager,
1161 empathy_chatroom_set_always_urgent (chatroom, active);
1162 g_object_unref (chatroom);
1167 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1168 EmpathyChatWindow *window)
1170 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1173 active = gtk_toggle_action_get_active (toggle_action);
1175 empathy_chat_set_show_contacts (priv->current_chat, active);
1179 chat_window_invite_participant_activate_cb (GtkAction *action,
1180 EmpathyChatWindow *window)
1182 EmpathyChatWindowPriv *priv;
1184 EmpathyTpChat *tp_chat;
1187 priv = GET_PRIV (window);
1189 g_return_if_fail (priv->current_chat != NULL);
1191 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1193 dialog = empathy_invite_participant_dialog_new (
1194 GTK_WINDOW (priv->dialog), tp_chat);
1195 gtk_widget_show (dialog);
1197 response = gtk_dialog_run (GTK_DIALOG (dialog));
1199 if (response == GTK_RESPONSE_ACCEPT) {
1200 TpContact *tp_contact;
1201 EmpathyContact *contact;
1203 tp_contact = empathy_invite_participant_dialog_get_selected (
1204 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1205 if (tp_contact == NULL) goto out;
1207 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1209 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1211 g_object_unref (contact);
1215 gtk_widget_destroy (dialog);
1219 chat_window_close_activate_cb (GtkAction *action,
1220 EmpathyChatWindow *window)
1222 EmpathyChatWindowPriv *priv;
1224 priv = GET_PRIV (window);
1226 g_return_if_fail (priv->current_chat != NULL);
1228 maybe_close_chat (window, priv->current_chat);
1232 chat_window_edit_activate_cb (GtkAction *action,
1233 EmpathyChatWindow *window)
1235 EmpathyChatWindowPriv *priv;
1236 GtkClipboard *clipboard;
1237 GtkTextBuffer *buffer;
1238 gboolean text_available;
1240 priv = GET_PRIV (window);
1242 g_return_if_fail (priv->current_chat != NULL);
1244 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1245 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1246 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1247 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1251 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1252 if (gtk_text_buffer_get_has_selection (buffer)) {
1253 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1254 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1258 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1260 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1261 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1264 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1265 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1266 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1270 chat_window_cut_activate_cb (GtkAction *action,
1271 EmpathyChatWindow *window)
1273 EmpathyChatWindowPriv *priv;
1275 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1277 priv = GET_PRIV (window);
1279 empathy_chat_cut (priv->current_chat);
1283 chat_window_copy_activate_cb (GtkAction *action,
1284 EmpathyChatWindow *window)
1286 EmpathyChatWindowPriv *priv;
1288 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1290 priv = GET_PRIV (window);
1292 empathy_chat_copy (priv->current_chat);
1296 chat_window_paste_activate_cb (GtkAction *action,
1297 EmpathyChatWindow *window)
1299 EmpathyChatWindowPriv *priv;
1301 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1303 priv = GET_PRIV (window);
1305 empathy_chat_paste (priv->current_chat);
1309 chat_window_find_activate_cb (GtkAction *action,
1310 EmpathyChatWindow *window)
1312 EmpathyChatWindowPriv *priv;
1314 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1316 priv = GET_PRIV (window);
1318 empathy_chat_find (priv->current_chat);
1322 chat_window_tabs_next_activate_cb (GtkAction *action,
1323 EmpathyChatWindow *window)
1325 EmpathyChatWindowPriv *priv;
1326 gint index_, numPages;
1327 gboolean wrap_around;
1329 priv = GET_PRIV (window);
1331 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1332 &wrap_around, NULL);
1334 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1335 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1337 if (index_ == (numPages - 1) && wrap_around) {
1338 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1342 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1346 chat_window_tabs_previous_activate_cb (GtkAction *action,
1347 EmpathyChatWindow *window)
1349 EmpathyChatWindowPriv *priv;
1350 gint index_, numPages;
1351 gboolean wrap_around;
1353 priv = GET_PRIV (window);
1355 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1356 &wrap_around, NULL);
1358 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1359 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1361 if (index_ <= 0 && wrap_around) {
1362 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1366 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1370 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1371 EmpathyChatWindow *window)
1373 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1374 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1375 empathy_get_current_action_time ());
1379 chat_window_tabs_left_activate_cb (GtkAction *action,
1380 EmpathyChatWindow *window)
1382 EmpathyChatWindowPriv *priv;
1384 gint index_, num_pages;
1386 priv = GET_PRIV (window);
1388 chat = priv->current_chat;
1389 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1394 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1398 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1399 chat_window_menu_context_update (priv, num_pages);
1403 chat_window_tabs_right_activate_cb (GtkAction *action,
1404 EmpathyChatWindow *window)
1406 EmpathyChatWindowPriv *priv;
1408 gint index_, num_pages;
1410 priv = GET_PRIV (window);
1412 chat = priv->current_chat;
1413 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1415 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1419 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1420 chat_window_menu_context_update (priv, num_pages);
1423 static EmpathyChatWindow *
1424 empathy_chat_window_new (void)
1426 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1430 chat_window_detach_activate_cb (GtkAction *action,
1431 EmpathyChatWindow *window)
1433 EmpathyChatWindowPriv *priv;
1434 EmpathyChatWindow *new_window;
1437 priv = GET_PRIV (window);
1439 chat = priv->current_chat;
1440 new_window = empathy_chat_window_new ();
1442 empathy_chat_window_move_chat (window, new_window, chat);
1444 priv = GET_PRIV (new_window);
1445 gtk_widget_show (priv->dialog);
1449 chat_window_help_contents_activate_cb (GtkAction *action,
1450 EmpathyChatWindow *window)
1452 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1454 empathy_url_show (priv->dialog, "help:empathy");
1458 chat_window_help_about_activate_cb (GtkAction *action,
1459 EmpathyChatWindow *window)
1461 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1463 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1467 chat_window_delete_event_cb (GtkWidget *dialog,
1469 EmpathyChatWindow *window)
1471 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1472 EmpathyChat *chat = NULL;
1476 DEBUG ("Delete event received");
1478 for (l = priv->chats; l != NULL; l = l->next) {
1479 if (chat_needs_close_confirmation (l->data)) {
1486 confirm_close (window, TRUE, n_rooms,
1487 (n_rooms == 1 ? chat : NULL));
1489 remove_all_chats (window);
1496 chat_window_composing_cb (EmpathyChat *chat,
1497 gboolean is_composing,
1498 EmpathyChatWindow *window)
1500 chat_window_update_chat_tab (chat);
1504 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1507 EmpathyChatWindowPriv *priv;
1509 priv = GET_PRIV (window);
1511 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1515 chat_window_notification_closed_cb (NotifyNotification *notify,
1516 EmpathyChatWindow *self)
1518 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1520 g_object_unref (notify);
1521 if (priv->notification == notify) {
1522 priv->notification = NULL;
1527 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1528 EmpathyMessage *message,
1531 EmpathyContact *sender;
1532 const gchar *header;
1536 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1537 gboolean res, has_x_canonical_append;
1538 NotifyNotification *notification = priv->notification;
1540 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1543 res = g_settings_get_boolean (priv->gsettings_notif,
1544 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1551 sender = empathy_message_get_sender (message);
1552 header = empathy_contact_get_alias (sender);
1553 body = empathy_message_get_body (message);
1554 escaped = g_markup_escape_text (body, -1);
1555 has_x_canonical_append = empathy_notify_manager_has_capability (
1556 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1558 if (notification != NULL && !has_x_canonical_append) {
1559 /* if the notification server supports x-canonical-append, it is
1560 better to not use notify_notification_update to avoid
1561 overwriting the current notification message */
1562 notify_notification_update (notification,
1563 header, escaped, NULL);
1565 /* if the notification server supports x-canonical-append,
1566 the hint will be added, so that the message from the
1567 just created notification will be automatically appended
1568 to an existing notification with the same title.
1569 In this way the previous message will not be lost: the new
1570 message will appear below it, in the same notification */
1571 notification = notify_notification_new (header, escaped, NULL);
1573 if (priv->notification == NULL) {
1574 priv->notification = notification;
1577 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1579 tp_g_signal_connect_object (notification, "closed",
1580 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1582 if (has_x_canonical_append) {
1583 /* We have to set a not empty string to keep libnotify happy */
1584 notify_notification_set_hint_string (notification,
1585 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1589 const gchar *category = empathy_chat_is_room (chat)
1590 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1591 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1592 notify_notification_set_hint (notification,
1593 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1594 g_variant_new_string (category));
1598 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1599 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1601 if (pixbuf != NULL) {
1602 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1603 g_object_unref (pixbuf);
1606 notify_notification_show (notification, NULL);
1612 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1614 EmpathyChatWindowPriv *priv;
1617 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1619 priv = GET_PRIV (window);
1621 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1627 chat_window_new_message_cb (EmpathyChat *chat,
1628 EmpathyMessage *message,
1630 gboolean should_highlight,
1631 EmpathyChatWindow *window)
1633 EmpathyChatWindowPriv *priv;
1635 gboolean needs_urgency;
1636 EmpathyContact *sender;
1638 priv = GET_PRIV (window);
1640 has_focus = empathy_chat_window_has_focus (window);
1642 /* - if we're the sender, we play the sound if it's specified in the
1643 * preferences and we're not away.
1644 * - if we receive a message, we play the sound if it's specified in the
1645 * preferences and the window does not have focus on the chat receiving
1649 sender = empathy_message_get_sender (message);
1651 if (empathy_contact_is_user (sender)) {
1652 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1653 EMPATHY_SOUND_MESSAGE_OUTGOING);
1656 if (has_focus && priv->current_chat == chat) {
1657 /* window and tab are focused so consider the message to be read */
1659 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1660 empathy_chat_messages_read (chat);
1664 /* Update the chat tab if this is the first unread message */
1665 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1666 chat_window_update_chat_tab (chat);
1669 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1670 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1671 * an unamed MUC (msn-like).
1672 * In case of a MUC, we set urgency if either:
1673 * a) the chatroom's always_urgent property is TRUE
1674 * b) the message contains our alias
1676 if (empathy_chat_is_room (chat)) {
1679 EmpathyChatroom *chatroom;
1681 account = empathy_chat_get_account (chat);
1682 room = empathy_chat_get_id (chat);
1684 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1687 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1688 needs_urgency = TRUE;
1690 needs_urgency = should_highlight;
1693 needs_urgency = TRUE;
1696 if (needs_urgency) {
1698 chat_window_set_urgency_hint (window, TRUE);
1701 /* Pending messages have already been displayed and notified in the
1702 * approver, so we don't display a notification and play a sound for those */
1704 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1705 EMPATHY_SOUND_MESSAGE_INCOMING);
1707 chat_window_show_or_update_notification (window, message, chat);
1711 /* update the number of unread messages and the window icon */
1712 chat_window_title_update (priv);
1713 chat_window_icon_update (priv, TRUE);
1717 chat_window_command_part (EmpathyChat *chat,
1720 EmpathyChat *chat_to_be_parted;
1721 EmpathyTpChat *tp_chat = NULL;
1723 if (strv[1] == NULL) {
1724 /* No chatroom ID specified */
1725 tp_chat = empathy_chat_get_tp_chat (chat);
1727 empathy_tp_chat_leave (tp_chat, "");
1730 chat_to_be_parted = empathy_chat_window_find_chat (
1731 empathy_chat_get_account (chat), strv[1], FALSE);
1733 if (chat_to_be_parted != NULL) {
1734 /* Found a chatroom matching the specified ID */
1735 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1737 empathy_tp_chat_leave (tp_chat, strv[2]);
1741 /* Going by the syntax of PART command:
1743 * /PART [<chatroom-ID>] [<reason>]
1745 * Chatroom-ID is not a must to specify a reason.
1746 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1747 * MUC then the current chatroom should be parted and srtv[1] should
1748 * be treated as part of the optional part-message. */
1749 message = g_strconcat (strv[1], " ", strv[2], NULL);
1750 tp_chat = empathy_chat_get_tp_chat (chat);
1752 empathy_tp_chat_leave (tp_chat, message);
1758 static GtkNotebook *
1759 notebook_create_window_cb (GtkNotebook *source,
1765 EmpathyChatWindowPriv *priv;
1766 EmpathyChatWindow *window, *new_window;
1769 chat = EMPATHY_CHAT (page);
1770 window = chat_window_find_chat (chat);
1772 new_window = empathy_chat_window_new ();
1773 priv = GET_PRIV (new_window);
1775 DEBUG ("Detach hook called");
1777 empathy_chat_window_move_chat (window, new_window, chat);
1779 gtk_widget_show (priv->dialog);
1780 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1786 chat_window_page_switched_cb (GtkNotebook *notebook,
1789 EmpathyChatWindow *window)
1791 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1792 EmpathyChat *chat = EMPATHY_CHAT (child);
1794 DEBUG ("Page switched");
1796 if (priv->page_added) {
1797 priv->page_added = FALSE;
1798 empathy_chat_scroll_down (chat);
1800 else if (priv->current_chat == chat) {
1804 priv->current_chat = chat;
1805 empathy_chat_messages_read (chat);
1807 chat_window_update_chat_tab (chat);
1811 chat_window_page_added_cb (GtkNotebook *notebook,
1814 EmpathyChatWindow *window)
1816 EmpathyChatWindowPriv *priv;
1819 priv = GET_PRIV (window);
1821 /* If we just received DND to the same window, we don't want
1822 * to do anything here like removing the tab and then readding
1823 * it, so we return here and in "page-added".
1825 if (priv->dnd_same_window) {
1826 DEBUG ("Page added (back to the same window)");
1827 priv->dnd_same_window = FALSE;
1831 DEBUG ("Page added");
1833 /* Get chat object */
1834 chat = EMPATHY_CHAT (child);
1836 /* Connect chat signals for this window */
1837 g_signal_connect (chat, "composing",
1838 G_CALLBACK (chat_window_composing_cb),
1840 g_signal_connect (chat, "new-message",
1841 G_CALLBACK (chat_window_new_message_cb),
1843 g_signal_connect (chat, "part-command-entered",
1844 G_CALLBACK (chat_window_command_part),
1846 g_signal_connect (chat, "notify::tp-chat",
1847 G_CALLBACK (chat_window_update_chat_tab),
1850 /* Set flag so we know to perform some special operations on
1851 * switch page due to the new page being added.
1853 priv->page_added = TRUE;
1855 /* Get list of chats up to date */
1856 priv->chats = g_list_append (priv->chats, chat);
1858 chat_window_update_chat_tab (chat);
1862 chat_window_page_removed_cb (GtkNotebook *notebook,
1865 EmpathyChatWindow *window)
1867 EmpathyChatWindowPriv *priv;
1870 priv = GET_PRIV (window);
1872 /* If we just received DND to the same window, we don't want
1873 * to do anything here like removing the tab and then readding
1874 * it, so we return here and in "page-added".
1876 if (priv->dnd_same_window) {
1877 DEBUG ("Page removed (and will be readded to same window)");
1881 DEBUG ("Page removed");
1883 /* Get chat object */
1884 chat = EMPATHY_CHAT (child);
1886 /* Disconnect all signal handlers for this chat and this window */
1887 g_signal_handlers_disconnect_by_func (chat,
1888 G_CALLBACK (chat_window_composing_cb),
1890 g_signal_handlers_disconnect_by_func (chat,
1891 G_CALLBACK (chat_window_new_message_cb),
1893 g_signal_handlers_disconnect_by_func (chat,
1894 G_CALLBACK (chat_window_update_chat_tab),
1897 /* Keep list of chats up to date */
1898 priv->chats = g_list_remove (priv->chats, chat);
1899 empathy_chat_messages_read (chat);
1901 if (priv->chats == NULL) {
1902 g_object_unref (window);
1904 chat_window_update (window, TRUE);
1909 chat_window_focus_in_event_cb (GtkWidget *widget,
1911 EmpathyChatWindow *window)
1913 EmpathyChatWindowPriv *priv;
1915 priv = GET_PRIV (window);
1917 empathy_chat_messages_read (priv->current_chat);
1919 chat_window_set_urgency_hint (window, FALSE);
1921 /* Update the title, since we now mark all unread messages as read. */
1922 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1928 chat_window_drag_drop (GtkWidget *widget,
1929 GdkDragContext *context,
1933 EmpathyChatWindow *window)
1936 EmpathyChatWindowPriv *priv;
1938 priv = GET_PRIV (window);
1940 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1941 if (target == GDK_NONE)
1942 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1944 if (target != GDK_NONE) {
1945 gtk_drag_get_data (widget, context, target, time_);
1953 chat_window_drag_motion (GtkWidget *widget,
1954 GdkDragContext *context,
1958 EmpathyChatWindow *window)
1961 EmpathyChatWindowPriv *priv;
1963 priv = GET_PRIV (window);
1965 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1966 if (target != GDK_NONE) {
1967 /* This is a file drag. Ensure the contact is online and set the
1968 drag type to COPY. Note that it's possible that the tab will
1969 be switched by GTK+ after a timeout from drag_motion without
1970 getting another drag_motion to disable the drop. You have
1971 to hold your mouse really still.
1973 EmpathyContact *contact;
1975 priv = GET_PRIV (window);
1976 contact = empathy_chat_get_remote_contact (priv->current_chat);
1977 /* contact is NULL for multi-user chats. We don't do
1978 * file transfers to MUCs. We also don't send files
1979 * to offline contacts or contacts that don't support
1982 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1983 gdk_drag_status (context, 0, time_);
1986 if (!(empathy_contact_get_capabilities (contact)
1987 & EMPATHY_CAPABILITIES_FT)) {
1988 gdk_drag_status (context, 0, time_);
1991 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1995 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1996 if (target != GDK_NONE) {
1997 /* This is a drag of a contact from a contact list. Set to COPY.
1998 FIXME: If this drag is to a MUC window, it invites the user.
1999 Otherwise, it opens a chat. Should we use a different drag
2000 type for invites? Should we allow ASK?
2002 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2010 drag_data_received_individual_id (EmpathyChatWindow *self,
2012 GdkDragContext *context,
2015 GtkSelectionData *selection,
2020 EmpathyIndividualManager *manager = NULL;
2021 FolksIndividual *individual;
2022 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2023 EmpathyTpChat *chat;
2024 TpContact *tp_contact;
2026 EmpathyContact *contact;
2028 id = (const gchar *) gtk_selection_data_get_data (selection);
2030 DEBUG ("DND invididual %s", id);
2032 if (priv->current_chat == NULL)
2035 chat = empathy_chat_get_tp_chat (priv->current_chat);
2039 if (!empathy_tp_chat_can_add_contact (chat)) {
2040 DEBUG ("Can't invite contact to %s",
2041 tp_proxy_get_object_path (chat));
2045 manager = empathy_individual_manager_dup_singleton ();
2047 individual = empathy_individual_manager_lookup_member (manager, id);
2048 if (individual == NULL) {
2049 DEBUG ("Failed to find individual %s", id);
2053 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2054 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2055 if (tp_contact == NULL) {
2056 DEBUG ("Can't find a TpContact on connection %s for %s",
2057 tp_proxy_get_object_path (conn), id);
2061 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2062 tp_channel_get_identifier ((TpChannel *) chat));
2064 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2065 empathy_tp_chat_add (chat, contact, NULL);
2066 g_object_unref (contact);
2069 gtk_drag_finish (context, TRUE, FALSE, time_);
2070 tp_clear_object (&manager);
2074 chat_window_drag_data_received (GtkWidget *widget,
2075 GdkDragContext *context,
2078 GtkSelectionData *selection,
2081 EmpathyChatWindow *window)
2083 if (info == DND_DRAG_TYPE_CONTACT_ID) {
2084 EmpathyChat *chat = NULL;
2085 EmpathyChatWindow *old_window;
2086 TpAccount *account = NULL;
2087 EmpathyClientFactory *factory;
2090 const gchar *account_id;
2091 const gchar *contact_id;
2093 id = (const gchar*) gtk_selection_data_get_data (selection);
2095 factory = empathy_client_factory_dup ();
2097 DEBUG ("DND contact from roster with id:'%s'", id);
2099 strv = g_strsplit (id, ":", 2);
2100 if (g_strv_length (strv) == 2) {
2101 account_id = strv[0];
2102 contact_id = strv[1];
2104 tp_simple_client_factory_ensure_account (
2105 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2108 g_object_unref (factory);
2109 if (account != NULL)
2110 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2113 if (account == NULL) {
2115 gtk_drag_finish (context, FALSE, FALSE, time_);
2120 empathy_chat_with_contact_id (
2121 account, contact_id,
2122 empathy_get_current_action_time (),
2130 old_window = chat_window_find_chat (chat);
2132 if (old_window == window) {
2133 gtk_drag_finish (context, TRUE, FALSE, time_);
2137 empathy_chat_window_move_chat (old_window, window, chat);
2139 empathy_chat_window_add_chat (window, chat);
2142 /* Added to take care of any outstanding chat events */
2143 empathy_chat_window_present_chat (chat,
2144 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2146 /* We should return TRUE to remove the data when doing
2147 * GDK_ACTION_MOVE, but we don't here otherwise it has
2148 * weird consequences, and we handle that internally
2149 * anyway with add_chat () and remove_chat ().
2151 gtk_drag_finish (context, TRUE, FALSE, time_);
2153 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2154 drag_data_received_individual_id (window, widget, context, x, y,
2155 selection, info, time_);
2157 else if (info == DND_DRAG_TYPE_URI_LIST) {
2158 EmpathyChatWindowPriv *priv;
2159 EmpathyContact *contact;
2162 priv = GET_PRIV (window);
2163 contact = empathy_chat_get_remote_contact (priv->current_chat);
2165 /* contact is NULL when current_chat is a multi-user chat.
2166 * We don't do file transfers to MUCs, so just cancel the drag.
2168 if (contact == NULL) {
2169 gtk_drag_finish (context, TRUE, FALSE, time_);
2173 data = (const gchar *) gtk_selection_data_get_data (selection);
2174 empathy_send_file_from_uri_list (contact, data);
2176 gtk_drag_finish (context, TRUE, FALSE, time_);
2178 else if (info == DND_DRAG_TYPE_TAB) {
2180 EmpathyChatWindow *old_window = NULL;
2184 chat = (void *) gtk_selection_data_get_data (selection);
2185 old_window = chat_window_find_chat (*chat);
2188 EmpathyChatWindowPriv *priv;
2190 priv = GET_PRIV (window);
2191 priv->dnd_same_window = (old_window == window);
2192 DEBUG ("DND tab (within same window: %s)",
2193 priv->dnd_same_window ? "Yes" : "No");
2196 DEBUG ("DND from unknown source");
2197 gtk_drag_finish (context, FALSE, FALSE, time_);
2202 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2203 guint num_chats_in_manager,
2204 EmpathyChatWindow *window)
2206 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2208 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2209 num_chats_in_manager > 0);
2213 chat_window_finalize (GObject *object)
2215 EmpathyChatWindow *window;
2216 EmpathyChatWindowPriv *priv;
2218 window = EMPATHY_CHAT_WINDOW (object);
2219 priv = GET_PRIV (window);
2221 DEBUG ("Finalized: %p", object);
2223 g_object_unref (priv->ui_manager);
2224 g_object_unref (priv->chatroom_manager);
2225 g_object_unref (priv->notify_mgr);
2226 g_object_unref (priv->gsettings_chat);
2227 g_object_unref (priv->gsettings_notif);
2228 g_object_unref (priv->gsettings_ui);
2229 g_object_unref (priv->sound_mgr);
2231 if (priv->notification != NULL) {
2232 notify_notification_close (priv->notification, NULL);
2233 priv->notification = NULL;
2236 if (priv->contact_targets) {
2237 gtk_target_list_unref (priv->contact_targets);
2239 if (priv->file_targets) {
2240 gtk_target_list_unref (priv->file_targets);
2243 if (priv->chat_manager) {
2244 g_signal_handler_disconnect (priv->chat_manager,
2245 priv->chat_manager_chats_changed_id);
2246 g_object_unref (priv->chat_manager);
2247 priv->chat_manager = NULL;
2250 chat_windows = g_list_remove (chat_windows, window);
2251 gtk_widget_destroy (priv->dialog);
2253 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2257 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2259 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2261 object_class->finalize = chat_window_finalize;
2263 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2267 empathy_chat_window_init (EmpathyChatWindow *window)
2270 GtkAccelGroup *accel_group;
2275 GtkWidget *chat_vbox;
2277 EmpathySmileyManager *smiley_manager;
2278 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2279 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2281 window->priv = priv;
2282 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2283 gui = empathy_builder_get_file (filename,
2284 "chat_window", &priv->dialog,
2285 "chat_vbox", &chat_vbox,
2286 "ui_manager", &priv->ui_manager,
2287 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2288 "menu_conv_favorite", &priv->menu_conv_favorite,
2289 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2290 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2291 "menu_edit_cut", &priv->menu_edit_cut,
2292 "menu_edit_copy", &priv->menu_edit_copy,
2293 "menu_edit_paste", &priv->menu_edit_paste,
2294 "menu_edit_find", &priv->menu_edit_find,
2295 "menu_tabs_next", &priv->menu_tabs_next,
2296 "menu_tabs_prev", &priv->menu_tabs_prev,
2297 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2298 "menu_tabs_left", &priv->menu_tabs_left,
2299 "menu_tabs_right", &priv->menu_tabs_right,
2300 "menu_tabs_detach", &priv->menu_tabs_detach,
2304 empathy_builder_connect (gui, window,
2305 "menu_conv", "activate", chat_window_conv_activate_cb,
2306 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2307 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2308 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2309 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2310 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2311 "menu_conv_close", "activate", chat_window_close_activate_cb,
2312 "menu_edit", "activate", chat_window_edit_activate_cb,
2313 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2314 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2315 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2316 "menu_edit_find", "activate", chat_window_find_activate_cb,
2317 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2318 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2319 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2320 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2321 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2322 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2323 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2324 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2327 g_object_ref (priv->ui_manager);
2328 g_object_unref (gui);
2330 empathy_set_css_provider (GTK_WIDGET (priv->dialog));
2332 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2333 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2334 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2335 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2337 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2339 priv->notebook = gtk_notebook_new ();
2341 g_signal_connect (priv->notebook, "create-window",
2342 G_CALLBACK (notebook_create_window_cb), window);
2344 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2345 "EmpathyChatWindow");
2346 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2347 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2348 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2349 gtk_widget_show (priv->notebook);
2352 accel_group = gtk_accel_group_new ();
2353 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2355 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2356 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2359 gtk_accel_group_connect (accel_group,
2366 g_object_unref (accel_group);
2368 /* Set up drag target lists */
2369 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2370 G_N_ELEMENTS (drag_types_dest_contact));
2371 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2372 G_N_ELEMENTS (drag_types_dest_file));
2374 /* Set up smiley menu */
2375 smiley_manager = empathy_smiley_manager_dup_singleton ();
2376 submenu = empathy_smiley_menu_new (smiley_manager,
2377 chat_window_insert_smiley_activate_cb,
2379 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2380 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2381 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2382 g_object_unref (smiley_manager);
2384 /* Set up signals we can't do with ui file since we may need to
2385 * block/unblock them at some later stage.
2388 g_signal_connect (priv->dialog,
2390 G_CALLBACK (chat_window_delete_event_cb),
2392 g_signal_connect (priv->dialog,
2394 G_CALLBACK (chat_window_focus_in_event_cb),
2396 g_signal_connect_after (priv->notebook,
2398 G_CALLBACK (chat_window_page_switched_cb),
2400 g_signal_connect (priv->notebook,
2402 G_CALLBACK (chat_window_page_added_cb),
2404 g_signal_connect (priv->notebook,
2406 G_CALLBACK (chat_window_page_removed_cb),
2409 /* Set up drag and drop */
2410 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2411 GTK_DEST_DEFAULT_HIGHLIGHT,
2413 G_N_ELEMENTS (drag_types_dest),
2414 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2416 /* connect_after to allow GtkNotebook's built-in tab switching */
2417 g_signal_connect_after (priv->notebook,
2419 G_CALLBACK (chat_window_drag_motion),
2421 g_signal_connect (priv->notebook,
2422 "drag-data-received",
2423 G_CALLBACK (chat_window_drag_data_received),
2425 g_signal_connect (priv->notebook,
2427 G_CALLBACK (chat_window_drag_drop),
2430 chat_windows = g_list_prepend (chat_windows, window);
2432 /* Set up private details */
2434 priv->current_chat = NULL;
2435 priv->notification = NULL;
2437 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2439 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2440 priv->chat_manager_chats_changed_id =
2441 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2442 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2445 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2446 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2450 /* Returns the window to open a new tab in if there is a suitable window,
2451 * otherwise, returns NULL indicating that a new window should be added.
2453 static EmpathyChatWindow *
2454 empathy_chat_window_get_default (gboolean room)
2456 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2458 gboolean separate_windows = TRUE;
2460 separate_windows = g_settings_get_boolean (gsettings,
2461 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2463 g_object_unref (gsettings);
2465 if (separate_windows) {
2466 /* Always create a new window */
2470 for (l = chat_windows; l; l = l->next) {
2471 EmpathyChatWindow *chat_window;
2472 guint nb_rooms, nb_private;
2474 chat_window = l->data;
2476 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2478 /* Skip the window if there aren't any rooms in it */
2479 if (room && nb_rooms == 0)
2482 /* Skip the window if there aren't any 1-1 chats in it */
2483 if (!room && nb_private == 0)
2493 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2496 EmpathyChatWindowPriv *priv;
2498 GtkWidget *popup_label;
2500 GValue value = { 0, };
2502 g_return_if_fail (window != NULL);
2503 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2505 priv = GET_PRIV (window);
2507 /* Reference the chat object */
2508 g_object_ref (chat);
2510 /* If this window has just been created, position it */
2511 if (priv->chats == NULL) {
2512 const gchar *name = "chat-window";
2513 gboolean separate_windows;
2515 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2516 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2518 if (empathy_chat_is_room (chat))
2519 name = "room-window";
2521 if (separate_windows) {
2524 /* Save current position of the window */
2525 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2527 /* First bind to the 'generic' name. So new window for which we didn't
2528 * save a geometry yet will have the geometry of the last saved
2529 * window (bgo #601191). */
2530 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2532 /* Restore previous position of the window so the newly created window
2533 * won't be in the same position as the latest saved window and so
2534 * completely hide it. */
2535 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2537 /* Then bind it to the name of the contact/room so we'll save the
2538 * geometry specific to this window */
2539 name = empathy_chat_get_id (chat);
2542 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2545 child = GTK_WIDGET (chat);
2546 label = chat_window_create_label (window, chat, TRUE);
2547 popup_label = chat_window_create_label (window, chat, FALSE);
2548 gtk_widget_show (child);
2550 g_signal_connect (chat, "notify::name",
2551 G_CALLBACK (chat_window_chat_notify_cb),
2553 g_signal_connect (chat, "notify::subject",
2554 G_CALLBACK (chat_window_chat_notify_cb),
2556 g_signal_connect (chat, "notify::remote-contact",
2557 G_CALLBACK (chat_window_chat_notify_cb),
2559 g_signal_connect (chat, "notify::sms-channel",
2560 G_CALLBACK (chat_window_chat_notify_cb),
2562 g_signal_connect (chat, "notify::n-messages-sending",
2563 G_CALLBACK (chat_window_chat_notify_cb),
2565 g_signal_connect (chat, "notify::nb-unread-messages",
2566 G_CALLBACK (chat_window_chat_notify_cb),
2568 chat_window_chat_notify_cb (chat);
2570 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2571 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2572 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2573 g_value_init (&value, G_TYPE_BOOLEAN);
2574 g_value_set_boolean (&value, TRUE);
2575 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2576 child, "tab-expand" , &value);
2577 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2578 child, "tab-fill" , &value);
2579 g_value_unset (&value);
2581 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2585 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2588 EmpathyChatWindowPriv *priv;
2590 EmpathyContact *remote_contact;
2591 EmpathyChatManager *chat_manager;
2593 g_return_if_fail (window != NULL);
2594 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2596 priv = GET_PRIV (window);
2598 g_signal_handlers_disconnect_by_func (chat,
2599 chat_window_chat_notify_cb,
2601 remote_contact = g_object_get_data (G_OBJECT (chat),
2602 "chat-window-remote-contact");
2603 if (remote_contact) {
2604 g_signal_handlers_disconnect_by_func (remote_contact,
2605 chat_window_update_chat_tab,
2609 chat_manager = empathy_chat_manager_dup_singleton ();
2610 empathy_chat_manager_closed_chat (chat_manager, chat);
2611 g_object_unref (chat_manager);
2613 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2615 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2617 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2619 g_object_unref (chat);
2623 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2624 EmpathyChatWindow *new_window,
2629 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2630 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2631 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2633 widget = GTK_WIDGET (chat);
2635 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2636 G_OBJECT (widget)->ref_count);
2638 /* We reference here to make sure we don't loose the widget
2639 * and the EmpathyChat object during the move.
2641 g_object_ref (chat);
2642 g_object_ref (widget);
2644 empathy_chat_window_remove_chat (old_window, chat);
2645 empathy_chat_window_add_chat (new_window, chat);
2647 g_object_unref (widget);
2648 g_object_unref (chat);
2652 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2655 EmpathyChatWindowPriv *priv;
2658 g_return_if_fail (window != NULL);
2659 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2661 priv = GET_PRIV (window);
2663 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2665 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2670 empathy_chat_window_find_chat (TpAccount *account,
2672 gboolean sms_channel)
2676 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2678 for (l = chat_windows; l; l = l->next) {
2679 EmpathyChatWindowPriv *priv;
2680 EmpathyChatWindow *window;
2684 priv = GET_PRIV (window);
2686 for (ll = priv->chats; ll; ll = ll->next) {
2691 if (account == empathy_chat_get_account (chat) &&
2692 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2693 sms_channel == empathy_chat_is_sms_channel (chat)) {
2703 empathy_chat_window_present_chat (EmpathyChat *chat,
2706 EmpathyChatWindow *window;
2707 EmpathyChatWindowPriv *priv;
2708 guint32 x_timestamp;
2710 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2712 window = chat_window_find_chat (chat);
2714 /* If the chat has no window, create one */
2715 if (window == NULL) {
2716 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2718 window = empathy_chat_window_new ();
2720 /* we want to display the newly created window even if we don't present
2722 priv = GET_PRIV (window);
2723 gtk_widget_show (priv->dialog);
2726 empathy_chat_window_add_chat (window, chat);
2729 /* Don't force the window to show itself when it wasn't
2730 * an action by the user
2732 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2735 priv = GET_PRIV (window);
2737 if (x_timestamp != GDK_CURRENT_TIME) {
2738 /* Don't present or switch tab if the action was earlier than the
2739 * last actions X time, accounting for overflow and the first ever
2742 if (priv->x_user_action_time != 0
2743 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2746 priv->x_user_action_time = x_timestamp;
2749 empathy_chat_window_switch_to_chat (window, chat);
2751 /* Don't use empathy_window_present_with_time () which would move the window
2752 * to our current desktop but move to the window's desktop instead. This is
2753 * more coherent with Shell's 'app is ready' notication which moves the view
2754 * to the app desktop rather than moving the app itself. */
2755 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2757 gtk_widget_grab_focus (chat->input_text_view);
2761 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2765 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2767 guint _nb_rooms = 0, _nb_private = 0;
2769 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2770 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2776 if (nb_rooms != NULL)
2777 *nb_rooms = _nb_rooms;
2778 if (nb_private != NULL)
2779 *nb_private = _nb_private;