1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2003-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Richard Hult <richard@imendio.com>
23 * Martyn Russell <martyn@imendio.com>
24 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
25 * Xavier Claessens <xclaesse@gmail.com>
26 * RĂ´mulo Fernandes Machado <romulo@castorgroup.net>
34 #include <gdk/gdkkeysyms.h>
36 #include <glib/gi18n.h>
37 #include <libnotify/notification.h>
39 #include <telepathy-glib/telepathy-glib.h>
41 #include <libempathy/empathy-client-factory.h>
42 #include <libempathy/empathy-contact.h>
43 #include <libempathy/empathy-message.h>
44 #include <libempathy/empathy-chatroom-manager.h>
45 #include <libempathy/empathy-gsettings.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-tp-contact-factory.h>
48 #include <libempathy/empathy-contact-list.h>
49 #include <libempathy/empathy-request-util.h>
51 #include <libempathy-gtk/empathy-images.h>
52 #include <libempathy-gtk/empathy-contact-dialogs.h>
53 #include <libempathy-gtk/empathy-log-window.h>
54 #include <libempathy-gtk/empathy-geometry.h>
55 #include <libempathy-gtk/empathy-smiley-manager.h>
56 #include <libempathy-gtk/empathy-sound-manager.h>
57 #include <libempathy-gtk/empathy-ui-utils.h>
58 #include <libempathy-gtk/empathy-notify-manager.h>
60 #include "empathy-chat-manager.h"
61 #include "empathy-chat-window.h"
62 #include "empathy-about-dialog.h"
63 #include "empathy-invite-participant-dialog.h"
64 #include "gedit-close-button.h"
66 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
67 #include <libempathy/empathy-debug.h>
69 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
71 #define X_EARLIER_OR_EQL(t1, t2) \
72 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
73 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
76 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
78 EmpathyChat *current_chat;
81 gboolean dnd_same_window;
82 EmpathyChatroomManager *chatroom_manager;
83 EmpathyNotifyManager *notify_mgr;
86 NotifyNotification *notification;
88 GtkTargetList *contact_targets;
89 GtkTargetList *file_targets;
91 EmpathyChatManager *chat_manager;
92 gulong chat_manager_chats_changed_id;
95 GtkUIManager *ui_manager;
96 GtkAction *menu_conv_insert_smiley;
97 GtkAction *menu_conv_favorite;
98 GtkAction *menu_conv_always_urgent;
99 GtkAction *menu_conv_toggle_contacts;
101 GtkAction *menu_edit_cut;
102 GtkAction *menu_edit_copy;
103 GtkAction *menu_edit_paste;
104 GtkAction *menu_edit_find;
106 GtkAction *menu_tabs_next;
107 GtkAction *menu_tabs_prev;
108 GtkAction *menu_tabs_undo_close_tab;
109 GtkAction *menu_tabs_left;
110 GtkAction *menu_tabs_right;
111 GtkAction *menu_tabs_detach;
113 /* Last user action time we acted upon to show a tab */
114 guint32 x_user_action_time;
116 GSettings *gsettings_chat;
117 GSettings *gsettings_notif;
118 GSettings *gsettings_ui;
120 EmpathySoundManager *sound_mgr;
121 } EmpathyChatWindowPriv;
123 static GList *chat_windows = NULL;
125 static const guint tab_accel_keys[] = {
126 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
127 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
131 DND_DRAG_TYPE_CONTACT_ID,
132 DND_DRAG_TYPE_URI_LIST,
136 static const GtkTargetEntry drag_types_dest[] = {
137 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_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 },
147 static const GtkTargetEntry drag_types_dest_file[] = {
148 /* must be first to be prioritized, in order to receive the
149 * note's file path from Tomboy instead of an URI */
150 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
151 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
154 static void chat_window_update (EmpathyChatWindow *window,
155 gboolean update_contact_menu);
157 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
160 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
163 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
164 EmpathyChatWindow *new_window,
167 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
171 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
174 chat_window_accel_cb (GtkAccelGroup *accelgroup,
178 EmpathyChatWindow *window)
180 EmpathyChatWindowPriv *priv;
184 priv = GET_PRIV (window);
186 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
187 if (tab_accel_keys[i] == key) {
194 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
198 static EmpathyChatWindow *
199 chat_window_find_chat (EmpathyChat *chat)
201 EmpathyChatWindowPriv *priv;
204 for (l = chat_windows; l; l = l->next) {
205 priv = GET_PRIV (l->data);
206 ll = g_list_find (priv->chats, chat);
216 remove_all_chats (EmpathyChatWindow *window)
218 EmpathyChatWindowPriv *priv;
220 priv = GET_PRIV (window);
221 g_object_ref (window);
223 while (priv->chats) {
224 empathy_chat_window_remove_chat (window, priv->chats->data);
227 g_object_unref (window);
231 confirm_close_response_cb (GtkWidget *dialog,
233 EmpathyChatWindow *window)
237 chat = g_object_get_data (G_OBJECT (dialog), "chat");
239 gtk_widget_destroy (dialog);
241 if (response != GTK_RESPONSE_ACCEPT)
245 empathy_chat_window_remove_chat (window, chat);
247 remove_all_chats (window);
252 confirm_close (EmpathyChatWindow *window,
253 gboolean close_window,
257 EmpathyChatWindowPriv *priv;
259 gchar *primary, *secondary;
261 g_return_if_fail (n_rooms > 0);
264 g_return_if_fail (chat == NULL);
266 g_return_if_fail (chat != NULL);
269 priv = GET_PRIV (window);
271 /* If there are no chats in this window, how could we possibly have got
274 g_return_if_fail (priv->chats != NULL);
276 /* Treat closing a window which only has one tab exactly like closing
279 if (close_window && priv->chats->next == NULL) {
280 close_window = FALSE;
281 chat = priv->chats->data;
285 primary = g_strdup (_("Close this window?"));
288 gchar *chat_name = empathy_chat_dup_name (chat);
289 secondary = g_strdup_printf (
290 _("Closing this window will leave %s. You will "
291 "not receive any further messages until you "
296 secondary = g_strdup_printf (
297 /* Note to translators: the number of chats will
298 * always be at least 2.
301 "Closing this window will leave a chat room. You will "
302 "not receive any further messages until you rejoin it.",
303 "Closing this window will leave %u chat rooms. You will "
304 "not receive any further messages until you rejoin them.",
309 gchar *chat_name = empathy_chat_dup_name (chat);
310 primary = g_strdup_printf (_("Leave %s?"), chat_name);
311 secondary = g_strdup (_("You will not receive any further messages from this chat "
312 "room until you rejoin it."));
316 dialog = gtk_message_dialog_new (
317 GTK_WINDOW (priv->dialog),
318 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
323 gtk_window_set_title (GTK_WINDOW (dialog), "");
324 g_object_set (dialog, "secondary-text", secondary, NULL);
329 gtk_dialog_add_button (GTK_DIALOG (dialog),
330 close_window ? _("Close window") : _("Leave room"),
331 GTK_RESPONSE_ACCEPT);
332 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
333 GTK_RESPONSE_ACCEPT);
336 g_object_set_data (G_OBJECT (dialog), "chat", chat);
339 g_signal_connect (dialog, "response",
340 G_CALLBACK (confirm_close_response_cb), window);
342 gtk_window_present (GTK_WINDOW (dialog));
345 /* Returns TRUE if we should check if the user really wants to leave. If it's
346 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
347 * the user is actually in the room as opposed to having been kicked or gone
348 * offline or something), then we should check.
351 chat_needs_close_confirmation (EmpathyChat *chat)
353 return (empathy_chat_is_room (chat)
354 && empathy_chat_get_tp_chat (chat) != NULL);
358 maybe_close_chat (EmpathyChatWindow *window,
361 g_return_if_fail (chat != NULL);
363 if (chat_needs_close_confirmation (chat)) {
364 confirm_close (window, FALSE, 1, chat);
366 empathy_chat_window_remove_chat (window, chat);
371 chat_window_close_clicked_cb (GtkAction *action,
374 EmpathyChatWindow *window;
376 window = chat_window_find_chat (chat);
377 maybe_close_chat (window, chat);
381 chat_tab_style_updated_cb (GtkWidget *hbox,
385 int char_width, h, w;
386 PangoContext *context;
387 const PangoFontDescription *font_desc;
388 PangoFontMetrics *metrics;
390 button = g_object_get_data (G_OBJECT (user_data),
391 "chat-window-tab-close-button");
392 context = gtk_widget_get_pango_context (hbox);
394 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
395 GTK_STATE_FLAG_NORMAL);
397 metrics = pango_context_get_metrics (context, font_desc,
398 pango_context_get_language (context));
399 char_width = pango_font_metrics_get_approximate_char_width (metrics);
400 pango_font_metrics_unref (metrics);
402 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
403 GTK_ICON_SIZE_MENU, &w, &h);
405 /* Request at least about 12 chars width plus at least space for the status
406 * image and the close button */
407 gtk_widget_set_size_request (hbox,
408 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
410 gtk_widget_set_size_request (button, w, h);
414 chat_window_create_label (EmpathyChatWindow *window,
416 gboolean is_tab_label)
419 GtkWidget *name_label;
420 GtkWidget *status_image;
421 GtkWidget *event_box;
422 GtkWidget *event_box_hbox;
423 PangoAttrList *attr_list;
424 PangoAttribute *attr;
426 /* The spacing between the button and the label. */
427 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
429 event_box = gtk_event_box_new ();
430 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
432 name_label = gtk_label_new (NULL);
434 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
436 attr_list = pango_attr_list_new ();
437 attr = pango_attr_scale_new (1/1.2);
438 attr->start_index = 0;
439 attr->end_index = -1;
440 pango_attr_list_insert (attr_list, attr);
441 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
442 pango_attr_list_unref (attr_list);
444 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
445 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
446 g_object_set_data (G_OBJECT (chat),
447 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
450 status_image = gtk_image_new ();
452 /* Spacing between the icon and label. */
453 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
455 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
456 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
458 g_object_set_data (G_OBJECT (chat),
459 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
461 g_object_set_data (G_OBJECT (chat),
462 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
465 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
466 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
469 GtkWidget *close_button;
470 GtkWidget *sending_spinner;
472 sending_spinner = gtk_spinner_new ();
474 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
476 g_object_set_data (G_OBJECT (chat),
477 "chat-window-tab-sending-spinner",
480 close_button = gedit_close_button_new ();
481 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
483 /* We don't want focus/keynav for the button to avoid clutter, and
484 * Ctrl-W works anyway.
486 gtk_widget_set_can_focus (close_button, FALSE);
487 gtk_widget_set_can_default (close_button, FALSE);
489 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
491 g_signal_connect (close_button,
493 G_CALLBACK (chat_window_close_clicked_cb),
496 /* React to theme changes and also setup the size correctly. */
497 g_signal_connect (hbox,
499 G_CALLBACK (chat_tab_style_updated_cb),
503 gtk_widget_show_all (hbox);
509 _submenu_notify_visible_changed_cb (GObject *object,
513 g_signal_handlers_disconnect_by_func (object,
514 _submenu_notify_visible_changed_cb,
516 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
520 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
525 gboolean wrap_around;
526 gboolean is_connected;
529 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
530 first_page = (page_num == 0);
531 last_page = (page_num == (num_pages - 1));
532 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
534 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
536 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
538 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
540 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
541 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
542 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
543 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
547 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
548 EmpathyChatWindow *self)
550 EmpathyTpChat *tp_chat;
551 TpConnection *connection;
553 gboolean sensitive = FALSE;
555 g_return_if_fail (priv->current_chat != NULL);
557 action = gtk_ui_manager_get_action (priv->ui_manager,
558 "/chats_menubar/menu_conv/menu_conv_invite_participant");
559 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
561 if (tp_chat != NULL) {
562 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
564 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
565 (tp_connection_get_status (connection, NULL) ==
566 TP_CONNECTION_STATUS_CONNECTED);
569 gtk_action_set_sensitive (action, sensitive);
573 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
574 EmpathyChatWindow *window)
576 GtkWidget *menu, *submenu, *orig_submenu;
578 menu = gtk_ui_manager_get_widget (priv->ui_manager,
579 "/chats_menubar/menu_contact");
580 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
582 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
583 submenu = empathy_chat_get_contact_menu (priv->current_chat);
585 if (submenu != NULL) {
586 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
587 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
589 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
590 gtk_widget_show (menu);
591 gtk_widget_set_sensitive (menu, TRUE);
593 gtk_widget_set_sensitive (menu, FALSE);
596 tp_g_signal_connect_object (orig_submenu,
598 (GCallback)_submenu_notify_visible_changed_cb,
604 get_all_unread_messages (EmpathyChatWindowPriv *priv)
609 for (l = priv->chats; l != NULL; l = g_list_next (l))
610 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
616 get_window_title_name (EmpathyChatWindowPriv *priv)
618 gchar *active_name, *ret;
620 guint current_unread_msgs;
622 nb_chats = g_list_length (priv->chats);
623 g_assert (nb_chats > 0);
625 active_name = empathy_chat_dup_name (priv->current_chat);
627 current_unread_msgs = empathy_chat_get_nb_unread_messages (
632 if (current_unread_msgs == 0)
633 ret = g_strdup (active_name);
635 ret = g_strdup_printf (ngettext (
637 "%s (%d unread)", current_unread_msgs),
638 active_name, current_unread_msgs);
640 guint nb_others = nb_chats - 1;
641 guint all_unread_msgs;
643 all_unread_msgs = get_all_unread_messages (priv);
645 if (all_unread_msgs == 0) {
646 /* no unread message */
647 ret = g_strdup_printf (ngettext (
649 "%s (and %u others)", nb_others),
650 active_name, nb_others);
653 else if (all_unread_msgs == current_unread_msgs) {
654 /* unread messages are in the current tab */
655 ret = g_strdup_printf (ngettext (
657 "%s (%d unread)", current_unread_msgs),
658 active_name, current_unread_msgs);
661 else if (current_unread_msgs == 0) {
662 /* unread messages are in other tabs */
663 ret = g_strdup_printf (ngettext (
664 "%s (%d unread from others)",
665 "%s (%d unread from others)",
667 active_name, all_unread_msgs);
671 /* unread messages are in all the tabs */
672 ret = g_strdup_printf (ngettext (
673 "%s (%d unread from all)",
674 "%s (%d unread from all)",
676 active_name, all_unread_msgs);
680 g_free (active_name);
686 chat_window_title_update (EmpathyChatWindowPriv *priv)
690 name = get_window_title_name (priv);
691 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
696 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
699 EmpathyContact *remote_contact;
700 gboolean avatar_in_icon;
703 n_chats = g_list_length (priv->chats);
705 /* Update window icon */
707 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
708 EMPATHY_IMAGE_MESSAGE);
710 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
711 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
713 if (n_chats == 1 && avatar_in_icon) {
714 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
715 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
716 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
719 g_object_unref (icon);
722 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
728 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
732 GtkWidget *chat_close_button;
735 if (num_pages == 1) {
736 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
737 chat_close_button = g_object_get_data (G_OBJECT (chat),
738 "chat-window-tab-close-button");
739 gtk_widget_hide (chat_close_button);
741 for (i=0; i<num_pages; i++) {
742 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
743 chat_close_button = g_object_get_data (G_OBJECT (chat),
744 "chat-window-tab-close-button");
745 gtk_widget_show (chat_close_button);
751 chat_window_update (EmpathyChatWindow *window,
752 gboolean update_contact_menu)
754 EmpathyChatWindowPriv *priv = GET_PRIV (window);
757 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
759 /* Update Tab menu */
760 chat_window_menu_context_update (priv,
763 chat_window_conversation_menu_update (priv, window);
765 /* If this update is due to a focus-in event, we know the menu will be
766 the same as when we last left it, so no work to do. Besides, if we
767 swap out the menu on a focus-in, we may confuse any external global
769 if (update_contact_menu) {
770 chat_window_contact_menu_update (priv,
774 chat_window_title_update (priv);
776 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
778 chat_window_close_button_update (priv,
783 append_markup_printf (GString *string,
790 va_start (args, format);
792 tmp = g_markup_vprintf_escaped (format, args);
793 g_string_append (string, tmp);
800 chat_window_update_chat_tab_full (EmpathyChat *chat,
801 gboolean update_contact_menu)
803 EmpathyChatWindow *window;
804 EmpathyChatWindowPriv *priv;
805 EmpathyContact *remote_contact;
809 const gchar *subject;
810 const gchar *status = NULL;
814 const gchar *icon_name;
815 GtkWidget *tab_image;
816 GtkWidget *menu_image;
817 GtkWidget *sending_spinner;
820 window = chat_window_find_chat (chat);
824 priv = GET_PRIV (window);
826 /* Get information */
827 name = empathy_chat_dup_name (chat);
828 account = empathy_chat_get_account (chat);
829 subject = empathy_chat_get_subject (chat);
830 remote_contact = empathy_chat_get_remote_contact (chat);
832 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
833 name, tp_proxy_get_object_path (account), subject, remote_contact);
835 /* Update tab image */
836 if (empathy_chat_get_tp_chat (chat) == NULL) {
837 /* No TpChat, we are disconnected */
840 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
841 icon_name = EMPATHY_IMAGE_MESSAGE;
843 else if (remote_contact && empathy_chat_is_composing (chat)) {
844 icon_name = EMPATHY_IMAGE_TYPING;
846 else if (empathy_chat_is_sms_channel (chat)) {
847 icon_name = EMPATHY_IMAGE_SMS;
849 else if (remote_contact) {
850 icon_name = empathy_icon_name_for_contact (remote_contact);
852 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
855 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
856 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
857 if (icon_name != NULL) {
858 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
859 gtk_widget_show (tab_image);
860 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
861 gtk_widget_show (menu_image);
863 gtk_widget_hide (tab_image);
864 gtk_widget_hide (menu_image);
867 /* Update the sending spinner */
868 nb_sending = empathy_chat_get_n_messages_sending (chat);
869 sending_spinner = g_object_get_data (G_OBJECT (chat),
870 "chat-window-tab-sending-spinner");
872 g_object_set (sending_spinner,
873 "active", nb_sending > 0,
874 "visible", nb_sending > 0,
877 /* Update tab tooltip */
878 tooltip = g_string_new (NULL);
880 if (remote_contact) {
881 id = empathy_contact_get_id (remote_contact);
882 status = empathy_contact_get_presence_message (remote_contact);
887 if (empathy_chat_is_sms_channel (chat)) {
888 append_markup_printf (tooltip, "%s ", _("SMS:"));
891 append_markup_printf (tooltip,
892 "<b>%s</b><small> (%s)</small>",
894 tp_account_get_display_name (account));
896 if (nb_sending > 0) {
897 char *tmp = g_strdup_printf (
898 ngettext ("Sending %d message",
899 "Sending %d messages",
903 g_string_append (tooltip, "\n");
904 g_string_append (tooltip, tmp);
906 gtk_widget_set_tooltip_text (sending_spinner, tmp);
910 if (!EMP_STR_EMPTY (status)) {
911 append_markup_printf (tooltip, "\n<i>%s</i>", status);
915 append_markup_printf (tooltip, "\n<b>%s</b> %s",
916 _("Topic:"), subject);
919 if (remote_contact && empathy_chat_is_composing (chat)) {
920 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
923 markup = g_string_free (tooltip, FALSE);
924 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
925 gtk_widget_set_tooltip_markup (widget, markup);
926 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
927 gtk_widget_set_tooltip_markup (widget, markup);
930 /* Update tab and menu label */
931 if (empathy_chat_is_highlighted (chat)) {
932 markup = g_markup_printf_escaped (
933 "<span color=\"red\" weight=\"bold\">%s</span>",
936 markup = g_markup_escape_text (name, -1);
939 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
940 gtk_label_set_markup (GTK_LABEL (widget), markup);
941 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
942 gtk_label_set_markup (GTK_LABEL (widget), markup);
945 /* Update the window if it's the current chat */
946 if (priv->current_chat == chat) {
947 chat_window_update (window, update_contact_menu);
954 chat_window_update_chat_tab (EmpathyChat *chat)
956 chat_window_update_chat_tab_full (chat, TRUE);
960 chat_window_chat_notify_cb (EmpathyChat *chat)
962 EmpathyChatWindow *window;
963 EmpathyContact *old_remote_contact;
964 EmpathyContact *remote_contact = NULL;
966 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
967 remote_contact = empathy_chat_get_remote_contact (chat);
969 if (old_remote_contact != remote_contact) {
970 /* The remote-contact associated with the chat changed, we need
971 * to keep track of any change of that contact and update the
972 * window each time. */
973 if (remote_contact) {
974 g_signal_connect_swapped (remote_contact, "notify",
975 G_CALLBACK (chat_window_update_chat_tab),
978 if (old_remote_contact) {
979 g_signal_handlers_disconnect_by_func (old_remote_contact,
980 chat_window_update_chat_tab,
984 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
985 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
988 chat_window_update_chat_tab (chat);
990 window = chat_window_find_chat (chat);
991 if (window != NULL) {
992 chat_window_update (window, FALSE);
997 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
998 EmpathySmiley *smiley,
1001 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1003 GtkTextBuffer *buffer;
1006 chat = priv->current_chat;
1008 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1009 gtk_text_buffer_get_end_iter (buffer, &iter);
1010 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1014 chat_window_conv_activate_cb (GtkAction *action,
1015 EmpathyChatWindow *window)
1017 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1020 EmpathyContact *remote_contact = NULL;
1022 /* Favorite room menu */
1023 is_room = empathy_chat_is_room (priv->current_chat);
1027 gboolean found = FALSE;
1028 EmpathyChatroom *chatroom;
1030 room = empathy_chat_get_id (priv->current_chat);
1031 account = empathy_chat_get_account (priv->current_chat);
1032 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1034 if (chatroom != NULL)
1035 found = empathy_chatroom_is_favorite (chatroom);
1037 DEBUG ("This room %s favorite", found ? "is" : "is not");
1038 gtk_toggle_action_set_active (
1039 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1041 if (chatroom != NULL)
1042 found = empathy_chatroom_is_always_urgent (chatroom);
1044 gtk_toggle_action_set_active (
1045 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1048 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1049 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1051 /* Show contacts menu */
1052 g_object_get (priv->current_chat,
1053 "remote-contact", &remote_contact,
1054 "show-contacts", &active,
1056 if (remote_contact == NULL) {
1057 gtk_toggle_action_set_active (
1058 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1061 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1062 (remote_contact == NULL));
1063 if (remote_contact != NULL) {
1064 g_object_unref (remote_contact);
1069 chat_window_clear_activate_cb (GtkAction *action,
1070 EmpathyChatWindow *window)
1072 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1074 empathy_chat_clear (priv->current_chat);
1078 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1079 EmpathyChatWindow *window)
1081 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1086 EmpathyChatroom *chatroom;
1088 active = gtk_toggle_action_get_active (toggle_action);
1089 account = empathy_chat_get_account (priv->current_chat);
1090 room = empathy_chat_get_id (priv->current_chat);
1091 name = empathy_chat_dup_name (priv->current_chat);
1093 chatroom = empathy_chatroom_manager_ensure_chatroom (
1094 priv->chatroom_manager,
1099 empathy_chatroom_set_favorite (chatroom, active);
1100 g_object_unref (chatroom);
1105 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1106 EmpathyChatWindow *window)
1108 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1113 EmpathyChatroom *chatroom;
1115 active = gtk_toggle_action_get_active (toggle_action);
1116 account = empathy_chat_get_account (priv->current_chat);
1117 room = empathy_chat_get_id (priv->current_chat);
1118 name = empathy_chat_dup_name (priv->current_chat);
1120 chatroom = empathy_chatroom_manager_ensure_chatroom (
1121 priv->chatroom_manager,
1126 empathy_chatroom_set_always_urgent (chatroom, active);
1127 g_object_unref (chatroom);
1132 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1133 EmpathyChatWindow *window)
1135 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1138 active = gtk_toggle_action_get_active (toggle_action);
1140 empathy_chat_set_show_contacts (priv->current_chat, active);
1144 chat_window_invite_participant_activate_cb (GtkAction *action,
1145 EmpathyChatWindow *window)
1147 EmpathyChatWindowPriv *priv;
1149 EmpathyTpChat *tp_chat;
1152 priv = GET_PRIV (window);
1154 g_return_if_fail (priv->current_chat != NULL);
1156 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1158 dialog = empathy_invite_participant_dialog_new (
1159 GTK_WINDOW (priv->dialog), tp_chat);
1160 gtk_widget_show (dialog);
1162 response = gtk_dialog_run (GTK_DIALOG (dialog));
1164 if (response == GTK_RESPONSE_ACCEPT) {
1165 TpContact *tp_contact;
1166 EmpathyContact *contact;
1168 tp_contact = empathy_invite_participant_dialog_get_selected (
1169 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1170 if (tp_contact == NULL) goto out;
1172 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1174 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1175 contact, _("Inviting you to this room"));
1177 g_object_unref (contact);
1181 gtk_widget_destroy (dialog);
1185 chat_window_close_activate_cb (GtkAction *action,
1186 EmpathyChatWindow *window)
1188 EmpathyChatWindowPriv *priv;
1190 priv = GET_PRIV (window);
1192 g_return_if_fail (priv->current_chat != NULL);
1194 maybe_close_chat (window, priv->current_chat);
1198 chat_window_edit_activate_cb (GtkAction *action,
1199 EmpathyChatWindow *window)
1201 EmpathyChatWindowPriv *priv;
1202 GtkClipboard *clipboard;
1203 GtkTextBuffer *buffer;
1204 gboolean text_available;
1206 priv = GET_PRIV (window);
1208 g_return_if_fail (priv->current_chat != NULL);
1210 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1211 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1212 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1213 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1217 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1218 if (gtk_text_buffer_get_has_selection (buffer)) {
1219 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1220 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1224 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1226 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1227 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1230 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1231 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1232 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1236 chat_window_cut_activate_cb (GtkAction *action,
1237 EmpathyChatWindow *window)
1239 EmpathyChatWindowPriv *priv;
1241 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1243 priv = GET_PRIV (window);
1245 empathy_chat_cut (priv->current_chat);
1249 chat_window_copy_activate_cb (GtkAction *action,
1250 EmpathyChatWindow *window)
1252 EmpathyChatWindowPriv *priv;
1254 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1256 priv = GET_PRIV (window);
1258 empathy_chat_copy (priv->current_chat);
1262 chat_window_paste_activate_cb (GtkAction *action,
1263 EmpathyChatWindow *window)
1265 EmpathyChatWindowPriv *priv;
1267 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1269 priv = GET_PRIV (window);
1271 empathy_chat_paste (priv->current_chat);
1275 chat_window_find_activate_cb (GtkAction *action,
1276 EmpathyChatWindow *window)
1278 EmpathyChatWindowPriv *priv;
1280 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1282 priv = GET_PRIV (window);
1284 empathy_chat_find (priv->current_chat);
1288 chat_window_tabs_next_activate_cb (GtkAction *action,
1289 EmpathyChatWindow *window)
1291 EmpathyChatWindowPriv *priv;
1292 gint index_, numPages;
1293 gboolean wrap_around;
1295 priv = GET_PRIV (window);
1297 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1298 &wrap_around, NULL);
1300 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1301 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1303 if (index_ == (numPages - 1) && wrap_around) {
1304 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1308 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1312 chat_window_tabs_previous_activate_cb (GtkAction *action,
1313 EmpathyChatWindow *window)
1315 EmpathyChatWindowPriv *priv;
1316 gint index_, numPages;
1317 gboolean wrap_around;
1319 priv = GET_PRIV (window);
1321 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1322 &wrap_around, NULL);
1324 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1325 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1327 if (index_ <= 0 && wrap_around) {
1328 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1332 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1336 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1337 EmpathyChatWindow *window)
1339 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1340 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1341 empathy_get_current_action_time ());
1345 chat_window_tabs_left_activate_cb (GtkAction *action,
1346 EmpathyChatWindow *window)
1348 EmpathyChatWindowPriv *priv;
1350 gint index_, num_pages;
1352 priv = GET_PRIV (window);
1354 chat = priv->current_chat;
1355 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1360 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1364 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1365 chat_window_menu_context_update (priv, num_pages);
1369 chat_window_tabs_right_activate_cb (GtkAction *action,
1370 EmpathyChatWindow *window)
1372 EmpathyChatWindowPriv *priv;
1374 gint index_, num_pages;
1376 priv = GET_PRIV (window);
1378 chat = priv->current_chat;
1379 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1381 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1385 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1386 chat_window_menu_context_update (priv, num_pages);
1389 static EmpathyChatWindow *
1390 empathy_chat_window_new (void)
1392 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1396 chat_window_detach_activate_cb (GtkAction *action,
1397 EmpathyChatWindow *window)
1399 EmpathyChatWindowPriv *priv;
1400 EmpathyChatWindow *new_window;
1403 priv = GET_PRIV (window);
1405 chat = priv->current_chat;
1406 new_window = empathy_chat_window_new ();
1408 empathy_chat_window_move_chat (window, new_window, chat);
1410 priv = GET_PRIV (new_window);
1411 gtk_widget_show (priv->dialog);
1415 chat_window_help_contents_activate_cb (GtkAction *action,
1416 EmpathyChatWindow *window)
1418 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1420 empathy_url_show (priv->dialog, "ghelp:empathy");
1424 chat_window_help_about_activate_cb (GtkAction *action,
1425 EmpathyChatWindow *window)
1427 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1429 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1433 chat_window_delete_event_cb (GtkWidget *dialog,
1435 EmpathyChatWindow *window)
1437 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1438 EmpathyChat *chat = NULL;
1442 DEBUG ("Delete event received");
1444 for (l = priv->chats; l != NULL; l = l->next) {
1445 if (chat_needs_close_confirmation (l->data)) {
1452 confirm_close (window, TRUE, n_rooms,
1453 (n_rooms == 1 ? chat : NULL));
1455 remove_all_chats (window);
1462 chat_window_composing_cb (EmpathyChat *chat,
1463 gboolean is_composing,
1464 EmpathyChatWindow *window)
1466 chat_window_update_chat_tab (chat);
1470 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1473 EmpathyChatWindowPriv *priv;
1475 priv = GET_PRIV (window);
1477 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1481 chat_window_notification_closed_cb (NotifyNotification *notify,
1482 EmpathyChatWindow *self)
1484 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1486 g_object_unref (notify);
1487 if (priv->notification == notify) {
1488 priv->notification = NULL;
1493 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1494 EmpathyMessage *message,
1497 EmpathyContact *sender;
1498 const gchar *header;
1502 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1503 gboolean res, has_x_canonical_append;
1504 NotifyNotification *notification = priv->notification;
1506 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1509 res = g_settings_get_boolean (priv->gsettings_notif,
1510 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1517 sender = empathy_message_get_sender (message);
1518 header = empathy_contact_get_alias (sender);
1519 body = empathy_message_get_body (message);
1520 escaped = g_markup_escape_text (body, -1);
1521 has_x_canonical_append = empathy_notify_manager_has_capability (
1522 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1524 if (notification != NULL && !has_x_canonical_append) {
1525 /* if the notification server supports x-canonical-append, it is
1526 better to not use notify_notification_update to avoid
1527 overwriting the current notification message */
1528 notify_notification_update (notification,
1529 header, escaped, NULL);
1531 /* if the notification server supports x-canonical-append,
1532 the hint will be added, so that the message from the
1533 just created notification will be automatically appended
1534 to an existing notification with the same title.
1535 In this way the previous message will not be lost: the new
1536 message will appear below it, in the same notification */
1537 notification = notify_notification_new (header, escaped, NULL);
1539 if (priv->notification == NULL) {
1540 priv->notification = notification;
1543 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1545 tp_g_signal_connect_object (notification, "closed",
1546 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1548 if (has_x_canonical_append) {
1549 /* We have to set a not empty string to keep libnotify happy */
1550 notify_notification_set_hint_string (notification,
1551 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1554 notify_notification_set_hint (notification,
1555 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1556 g_variant_new_string ("im.received"));
1559 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1560 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1562 if (pixbuf != NULL) {
1563 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1564 g_object_unref (pixbuf);
1567 notify_notification_show (notification, NULL);
1573 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1575 EmpathyChatWindowPriv *priv;
1578 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1580 priv = GET_PRIV (window);
1582 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1588 chat_window_new_message_cb (EmpathyChat *chat,
1589 EmpathyMessage *message,
1591 gboolean should_highlight,
1592 EmpathyChatWindow *window)
1594 EmpathyChatWindowPriv *priv;
1596 gboolean needs_urgency;
1597 EmpathyContact *sender;
1599 priv = GET_PRIV (window);
1601 has_focus = empathy_chat_window_has_focus (window);
1603 /* - if we're the sender, we play the sound if it's specified in the
1604 * preferences and we're not away.
1605 * - if we receive a message, we play the sound if it's specified in the
1606 * preferences and the window does not have focus on the chat receiving
1610 sender = empathy_message_get_sender (message);
1612 if (empathy_contact_is_user (sender)) {
1613 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1614 EMPATHY_SOUND_MESSAGE_OUTGOING);
1617 if (has_focus && priv->current_chat == chat) {
1618 /* window and tab are focused so consider the message to be read */
1620 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1621 empathy_chat_messages_read (chat);
1625 /* Update the chat tab if this is the first unread message */
1626 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1627 chat_window_update_chat_tab (chat);
1630 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1631 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1632 * an unamed MUC (msn-like).
1633 * In case of a MUC, we set urgency if either:
1634 * a) the chatroom's always_urgent property is TRUE
1635 * b) the message contains our alias
1637 if (empathy_chat_is_room (chat)) {
1640 EmpathyChatroom *chatroom;
1642 account = empathy_chat_get_account (chat);
1643 room = empathy_chat_get_id (chat);
1645 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1648 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1649 needs_urgency = TRUE;
1651 needs_urgency = should_highlight;
1654 needs_urgency = TRUE;
1657 if (needs_urgency) {
1659 chat_window_set_urgency_hint (window, TRUE);
1662 /* Pending messages have already been displayed and notified in the
1663 * approver, so we don't display a notification and play a sound for those */
1665 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1666 EMPATHY_SOUND_MESSAGE_INCOMING);
1668 chat_window_show_or_update_notification (window, message, chat);
1672 /* update the number of unread messages and the window icon */
1673 chat_window_title_update (priv);
1674 chat_window_icon_update (priv, TRUE);
1678 chat_window_command_part (EmpathyChat *chat,
1681 EmpathyChat *chat_to_be_parted;
1682 EmpathyTpChat *tp_chat = NULL;
1684 if (strv[1] == NULL) {
1685 /* No chatroom ID specified */
1686 tp_chat = empathy_chat_get_tp_chat (chat);
1688 empathy_tp_chat_leave (tp_chat, "");
1691 chat_to_be_parted = empathy_chat_window_find_chat (
1692 empathy_chat_get_account (chat), strv[1], FALSE);
1694 if (chat_to_be_parted != NULL) {
1695 /* Found a chatroom matching the specified ID */
1696 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1698 empathy_tp_chat_leave (tp_chat, strv[2]);
1702 /* Going by the syntax of PART command:
1704 * /PART [<chatroom-ID>] [<reason>]
1706 * Chatroom-ID is not a must to specify a reason.
1707 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1708 * MUC then the current chatroom should be parted and srtv[1] should
1709 * be treated as part of the optional part-message. */
1710 message = g_strconcat (strv[1], " ", strv[2], NULL);
1711 tp_chat = empathy_chat_get_tp_chat (chat);
1713 empathy_tp_chat_leave (tp_chat, message);
1719 static GtkNotebook *
1720 notebook_create_window_cb (GtkNotebook *source,
1726 EmpathyChatWindowPriv *priv;
1727 EmpathyChatWindow *window, *new_window;
1730 chat = EMPATHY_CHAT (page);
1731 window = chat_window_find_chat (chat);
1733 new_window = empathy_chat_window_new ();
1734 priv = GET_PRIV (new_window);
1736 DEBUG ("Detach hook called");
1738 empathy_chat_window_move_chat (window, new_window, chat);
1740 gtk_widget_show (priv->dialog);
1741 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1747 chat_window_page_switched_cb (GtkNotebook *notebook,
1750 EmpathyChatWindow *window)
1752 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1753 EmpathyChat *chat = EMPATHY_CHAT (child);
1755 DEBUG ("Page switched");
1757 if (priv->page_added) {
1758 priv->page_added = FALSE;
1759 empathy_chat_scroll_down (chat);
1761 else if (priv->current_chat == chat) {
1765 priv->current_chat = chat;
1766 empathy_chat_messages_read (chat);
1768 chat_window_update_chat_tab (chat);
1772 chat_window_page_added_cb (GtkNotebook *notebook,
1775 EmpathyChatWindow *window)
1777 EmpathyChatWindowPriv *priv;
1780 priv = GET_PRIV (window);
1782 /* If we just received DND to the same window, we don't want
1783 * to do anything here like removing the tab and then readding
1784 * it, so we return here and in "page-added".
1786 if (priv->dnd_same_window) {
1787 DEBUG ("Page added (back to the same window)");
1788 priv->dnd_same_window = FALSE;
1792 DEBUG ("Page added");
1794 /* Get chat object */
1795 chat = EMPATHY_CHAT (child);
1797 /* Connect chat signals for this window */
1798 g_signal_connect (chat, "composing",
1799 G_CALLBACK (chat_window_composing_cb),
1801 g_signal_connect (chat, "new-message",
1802 G_CALLBACK (chat_window_new_message_cb),
1804 g_signal_connect (chat, "part-command-entered",
1805 G_CALLBACK (chat_window_command_part),
1807 g_signal_connect (chat, "notify::tp-chat",
1808 G_CALLBACK (chat_window_update_chat_tab),
1811 /* Set flag so we know to perform some special operations on
1812 * switch page due to the new page being added.
1814 priv->page_added = TRUE;
1816 /* Get list of chats up to date */
1817 priv->chats = g_list_append (priv->chats, chat);
1819 chat_window_update_chat_tab (chat);
1823 chat_window_page_removed_cb (GtkNotebook *notebook,
1826 EmpathyChatWindow *window)
1828 EmpathyChatWindowPriv *priv;
1831 priv = GET_PRIV (window);
1833 /* If we just received DND to the same window, we don't want
1834 * to do anything here like removing the tab and then readding
1835 * it, so we return here and in "page-added".
1837 if (priv->dnd_same_window) {
1838 DEBUG ("Page removed (and will be readded to same window)");
1842 DEBUG ("Page removed");
1844 /* Get chat object */
1845 chat = EMPATHY_CHAT (child);
1847 /* Disconnect all signal handlers for this chat and this window */
1848 g_signal_handlers_disconnect_by_func (chat,
1849 G_CALLBACK (chat_window_composing_cb),
1851 g_signal_handlers_disconnect_by_func (chat,
1852 G_CALLBACK (chat_window_new_message_cb),
1854 g_signal_handlers_disconnect_by_func (chat,
1855 G_CALLBACK (chat_window_update_chat_tab),
1858 /* Keep list of chats up to date */
1859 priv->chats = g_list_remove (priv->chats, chat);
1860 empathy_chat_messages_read (chat);
1862 if (priv->chats == NULL) {
1863 g_object_unref (window);
1865 chat_window_update (window, TRUE);
1870 chat_window_focus_in_event_cb (GtkWidget *widget,
1872 EmpathyChatWindow *window)
1874 EmpathyChatWindowPriv *priv;
1876 priv = GET_PRIV (window);
1878 empathy_chat_messages_read (priv->current_chat);
1880 chat_window_set_urgency_hint (window, FALSE);
1882 /* Update the title, since we now mark all unread messages as read. */
1883 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1889 chat_window_drag_drop (GtkWidget *widget,
1890 GdkDragContext *context,
1894 EmpathyChatWindow *window)
1897 EmpathyChatWindowPriv *priv;
1899 priv = GET_PRIV (window);
1901 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1902 if (target == GDK_NONE)
1903 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1905 if (target != GDK_NONE) {
1906 gtk_drag_get_data (widget, context, target, time_);
1914 chat_window_drag_motion (GtkWidget *widget,
1915 GdkDragContext *context,
1919 EmpathyChatWindow *window)
1922 EmpathyChatWindowPriv *priv;
1924 priv = GET_PRIV (window);
1926 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1927 if (target != GDK_NONE) {
1928 /* This is a file drag. Ensure the contact is online and set the
1929 drag type to COPY. Note that it's possible that the tab will
1930 be switched by GTK+ after a timeout from drag_motion without
1931 getting another drag_motion to disable the drop. You have
1932 to hold your mouse really still.
1934 EmpathyContact *contact;
1936 priv = GET_PRIV (window);
1937 contact = empathy_chat_get_remote_contact (priv->current_chat);
1938 /* contact is NULL for multi-user chats. We don't do
1939 * file transfers to MUCs. We also don't send files
1940 * to offline contacts or contacts that don't support
1943 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1944 gdk_drag_status (context, 0, time_);
1947 if (!(empathy_contact_get_capabilities (contact)
1948 & EMPATHY_CAPABILITIES_FT)) {
1949 gdk_drag_status (context, 0, time_);
1952 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1956 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1957 if (target != GDK_NONE) {
1958 /* This is a drag of a contact from a contact list. Set to COPY.
1959 FIXME: If this drag is to a MUC window, it invites the user.
1960 Otherwise, it opens a chat. Should we use a different drag
1961 type for invites? Should we allow ASK?
1963 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1971 chat_window_drag_data_received (GtkWidget *widget,
1972 GdkDragContext *context,
1975 GtkSelectionData *selection,
1978 EmpathyChatWindow *window)
1980 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1981 EmpathyChat *chat = NULL;
1982 EmpathyChatWindow *old_window;
1983 TpAccount *account = NULL;
1984 EmpathyClientFactory *factory;
1987 const gchar *account_id;
1988 const gchar *contact_id;
1990 id = (const gchar*) gtk_selection_data_get_data (selection);
1992 factory = empathy_client_factory_dup ();
1994 DEBUG ("DND contact from roster with id:'%s'", id);
1996 strv = g_strsplit (id, ":", 2);
1997 if (g_strv_length (strv) == 2) {
1998 account_id = strv[0];
1999 contact_id = strv[1];
2001 tp_simple_client_factory_ensure_account (
2002 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2005 g_object_unref (factory);
2006 if (account != NULL)
2007 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2010 if (account == NULL) {
2012 gtk_drag_finish (context, FALSE, FALSE, time_);
2017 empathy_chat_with_contact_id (
2018 account, contact_id,
2019 empathy_get_current_action_time (),
2027 old_window = chat_window_find_chat (chat);
2029 if (old_window == window) {
2030 gtk_drag_finish (context, TRUE, FALSE, time_);
2034 empathy_chat_window_move_chat (old_window, window, chat);
2036 empathy_chat_window_add_chat (window, chat);
2039 /* Added to take care of any outstanding chat events */
2040 empathy_chat_window_present_chat (chat,
2041 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2043 /* We should return TRUE to remove the data when doing
2044 * GDK_ACTION_MOVE, but we don't here otherwise it has
2045 * weird consequences, and we handle that internally
2046 * anyway with add_chat () and remove_chat ().
2048 gtk_drag_finish (context, TRUE, FALSE, time_);
2050 else if (info == DND_DRAG_TYPE_URI_LIST) {
2051 EmpathyChatWindowPriv *priv;
2052 EmpathyContact *contact;
2055 priv = GET_PRIV (window);
2056 contact = empathy_chat_get_remote_contact (priv->current_chat);
2058 /* contact is NULL when current_chat is a multi-user chat.
2059 * We don't do file transfers to MUCs, so just cancel the drag.
2061 if (contact == NULL) {
2062 gtk_drag_finish (context, TRUE, FALSE, time_);
2066 data = (const gchar *) gtk_selection_data_get_data (selection);
2067 empathy_send_file_from_uri_list (contact, data);
2069 gtk_drag_finish (context, TRUE, FALSE, time_);
2071 else if (info == DND_DRAG_TYPE_TAB) {
2073 EmpathyChatWindow *old_window = NULL;
2077 chat = (void *) gtk_selection_data_get_data (selection);
2078 old_window = chat_window_find_chat (*chat);
2081 EmpathyChatWindowPriv *priv;
2083 priv = GET_PRIV (window);
2084 priv->dnd_same_window = (old_window == window);
2085 DEBUG ("DND tab (within same window: %s)",
2086 priv->dnd_same_window ? "Yes" : "No");
2089 DEBUG ("DND from unknown source");
2090 gtk_drag_finish (context, FALSE, FALSE, time_);
2095 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2096 guint num_chats_in_manager,
2097 EmpathyChatWindow *window)
2099 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2101 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2102 num_chats_in_manager > 0);
2106 chat_window_finalize (GObject *object)
2108 EmpathyChatWindow *window;
2109 EmpathyChatWindowPriv *priv;
2111 window = EMPATHY_CHAT_WINDOW (object);
2112 priv = GET_PRIV (window);
2114 DEBUG ("Finalized: %p", object);
2116 g_object_unref (priv->ui_manager);
2117 g_object_unref (priv->chatroom_manager);
2118 g_object_unref (priv->notify_mgr);
2119 g_object_unref (priv->gsettings_chat);
2120 g_object_unref (priv->gsettings_notif);
2121 g_object_unref (priv->gsettings_ui);
2122 g_object_unref (priv->sound_mgr);
2124 if (priv->notification != NULL) {
2125 notify_notification_close (priv->notification, NULL);
2126 priv->notification = NULL;
2129 if (priv->contact_targets) {
2130 gtk_target_list_unref (priv->contact_targets);
2132 if (priv->file_targets) {
2133 gtk_target_list_unref (priv->file_targets);
2136 if (priv->chat_manager) {
2137 g_signal_handler_disconnect (priv->chat_manager,
2138 priv->chat_manager_chats_changed_id);
2139 g_object_unref (priv->chat_manager);
2140 priv->chat_manager = NULL;
2143 chat_windows = g_list_remove (chat_windows, window);
2144 gtk_widget_destroy (priv->dialog);
2146 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2150 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2152 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2154 object_class->finalize = chat_window_finalize;
2156 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2160 empathy_chat_window_init (EmpathyChatWindow *window)
2163 GtkAccelGroup *accel_group;
2168 GtkWidget *chat_vbox;
2170 EmpathySmileyManager *smiley_manager;
2171 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2172 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2174 window->priv = priv;
2175 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2176 gui = empathy_builder_get_file (filename,
2177 "chat_window", &priv->dialog,
2178 "chat_vbox", &chat_vbox,
2179 "ui_manager", &priv->ui_manager,
2180 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2181 "menu_conv_favorite", &priv->menu_conv_favorite,
2182 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2183 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2184 "menu_edit_cut", &priv->menu_edit_cut,
2185 "menu_edit_copy", &priv->menu_edit_copy,
2186 "menu_edit_paste", &priv->menu_edit_paste,
2187 "menu_edit_find", &priv->menu_edit_find,
2188 "menu_tabs_next", &priv->menu_tabs_next,
2189 "menu_tabs_prev", &priv->menu_tabs_prev,
2190 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2191 "menu_tabs_left", &priv->menu_tabs_left,
2192 "menu_tabs_right", &priv->menu_tabs_right,
2193 "menu_tabs_detach", &priv->menu_tabs_detach,
2197 empathy_builder_connect (gui, window,
2198 "menu_conv", "activate", chat_window_conv_activate_cb,
2199 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2200 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2201 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2202 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2203 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2204 "menu_conv_close", "activate", chat_window_close_activate_cb,
2205 "menu_edit", "activate", chat_window_edit_activate_cb,
2206 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2207 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2208 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2209 "menu_edit_find", "activate", chat_window_find_activate_cb,
2210 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2211 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2212 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2213 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2214 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2215 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2216 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2217 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2220 g_object_ref (priv->ui_manager);
2221 g_object_unref (gui);
2223 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2224 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2225 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2226 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2228 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2230 priv->notebook = gtk_notebook_new ();
2232 g_signal_connect (priv->notebook, "create-window",
2233 G_CALLBACK (notebook_create_window_cb), window);
2235 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2236 "EmpathyChatWindow");
2237 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2238 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2239 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2240 gtk_widget_show (priv->notebook);
2243 accel_group = gtk_accel_group_new ();
2244 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2246 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2247 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2250 gtk_accel_group_connect (accel_group,
2257 g_object_unref (accel_group);
2259 /* Set up drag target lists */
2260 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2261 G_N_ELEMENTS (drag_types_dest_contact));
2262 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2263 G_N_ELEMENTS (drag_types_dest_file));
2265 /* Set up smiley menu */
2266 smiley_manager = empathy_smiley_manager_dup_singleton ();
2267 submenu = empathy_smiley_menu_new (smiley_manager,
2268 chat_window_insert_smiley_activate_cb,
2270 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2271 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2272 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2273 g_object_unref (smiley_manager);
2275 /* Set up signals we can't do with ui file since we may need to
2276 * block/unblock them at some later stage.
2279 g_signal_connect (priv->dialog,
2281 G_CALLBACK (chat_window_delete_event_cb),
2283 g_signal_connect (priv->dialog,
2285 G_CALLBACK (chat_window_focus_in_event_cb),
2287 g_signal_connect_after (priv->notebook,
2289 G_CALLBACK (chat_window_page_switched_cb),
2291 g_signal_connect (priv->notebook,
2293 G_CALLBACK (chat_window_page_added_cb),
2295 g_signal_connect (priv->notebook,
2297 G_CALLBACK (chat_window_page_removed_cb),
2300 /* Set up drag and drop */
2301 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2302 GTK_DEST_DEFAULT_HIGHLIGHT,
2304 G_N_ELEMENTS (drag_types_dest),
2305 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2307 /* connect_after to allow GtkNotebook's built-in tab switching */
2308 g_signal_connect_after (priv->notebook,
2310 G_CALLBACK (chat_window_drag_motion),
2312 g_signal_connect (priv->notebook,
2313 "drag-data-received",
2314 G_CALLBACK (chat_window_drag_data_received),
2316 g_signal_connect (priv->notebook,
2318 G_CALLBACK (chat_window_drag_drop),
2321 chat_windows = g_list_prepend (chat_windows, window);
2323 /* Set up private details */
2325 priv->current_chat = NULL;
2326 priv->notification = NULL;
2328 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2330 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2331 priv->chat_manager_chats_changed_id =
2332 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2333 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2336 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2337 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2341 /* Returns the window to open a new tab in if there is a suitable window,
2342 * otherwise, returns NULL indicating that a new window should be added.
2344 static EmpathyChatWindow *
2345 empathy_chat_window_get_default (gboolean room)
2347 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2349 gboolean separate_windows = TRUE;
2351 separate_windows = g_settings_get_boolean (gsettings,
2352 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2354 g_object_unref (gsettings);
2356 if (separate_windows) {
2357 /* Always create a new window */
2361 for (l = chat_windows; l; l = l->next) {
2362 EmpathyChatWindow *chat_window;
2363 guint nb_rooms, nb_private;
2365 chat_window = l->data;
2367 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2369 /* Skip the window if there aren't any rooms in it */
2370 if (room && nb_rooms == 0)
2373 /* Skip the window if there aren't any 1-1 chats in it */
2374 if (!room && nb_private == 0)
2384 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2387 EmpathyChatWindowPriv *priv;
2389 GtkWidget *popup_label;
2391 GValue value = { 0, };
2393 g_return_if_fail (window != NULL);
2394 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2396 priv = GET_PRIV (window);
2398 /* Reference the chat object */
2399 g_object_ref (chat);
2401 /* If this window has just been created, position it */
2402 if (priv->chats == NULL) {
2403 const gchar *name = "chat-window";
2404 gboolean separate_windows;
2406 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2407 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2409 if (empathy_chat_is_room (chat))
2410 name = "room-window";
2412 if (separate_windows) {
2415 /* Save current position of the window */
2416 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2418 /* First bind to the 'generic' name. So new window for which we didn't
2419 * save a geometry yet will have the geometry of the last saved
2420 * window (bgo #601191). */
2421 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2423 /* Restore previous position of the window so the newly created window
2424 * won't be in the same position as the latest saved window and so
2425 * completely hide it. */
2426 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2428 /* Then bind it to the name of the contact/room so we'll save the
2429 * geometry specific to this window */
2430 name = empathy_chat_get_id (chat);
2433 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2436 child = GTK_WIDGET (chat);
2437 label = chat_window_create_label (window, chat, TRUE);
2438 popup_label = chat_window_create_label (window, chat, FALSE);
2439 gtk_widget_show (child);
2441 g_signal_connect (chat, "notify::name",
2442 G_CALLBACK (chat_window_chat_notify_cb),
2444 g_signal_connect (chat, "notify::subject",
2445 G_CALLBACK (chat_window_chat_notify_cb),
2447 g_signal_connect (chat, "notify::remote-contact",
2448 G_CALLBACK (chat_window_chat_notify_cb),
2450 g_signal_connect (chat, "notify::sms-channel",
2451 G_CALLBACK (chat_window_chat_notify_cb),
2453 g_signal_connect (chat, "notify::n-messages-sending",
2454 G_CALLBACK (chat_window_chat_notify_cb),
2456 g_signal_connect (chat, "notify::nb-unread-messages",
2457 G_CALLBACK (chat_window_chat_notify_cb),
2459 chat_window_chat_notify_cb (chat);
2461 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2462 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2463 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2464 g_value_init (&value, G_TYPE_BOOLEAN);
2465 g_value_set_boolean (&value, TRUE);
2466 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2467 child, "tab-expand" , &value);
2468 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2469 child, "tab-fill" , &value);
2470 g_value_unset (&value);
2472 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2476 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2479 EmpathyChatWindowPriv *priv;
2481 EmpathyContact *remote_contact;
2482 EmpathyChatManager *chat_manager;
2484 g_return_if_fail (window != NULL);
2485 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2487 priv = GET_PRIV (window);
2489 g_signal_handlers_disconnect_by_func (chat,
2490 chat_window_chat_notify_cb,
2492 remote_contact = g_object_get_data (G_OBJECT (chat),
2493 "chat-window-remote-contact");
2494 if (remote_contact) {
2495 g_signal_handlers_disconnect_by_func (remote_contact,
2496 chat_window_update_chat_tab,
2500 chat_manager = empathy_chat_manager_dup_singleton ();
2501 empathy_chat_manager_closed_chat (chat_manager, chat);
2502 g_object_unref (chat_manager);
2504 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2506 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2508 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2510 g_object_unref (chat);
2514 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2515 EmpathyChatWindow *new_window,
2520 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2521 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2522 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2524 widget = GTK_WIDGET (chat);
2526 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2527 G_OBJECT (widget)->ref_count);
2529 /* We reference here to make sure we don't loose the widget
2530 * and the EmpathyChat object during the move.
2532 g_object_ref (chat);
2533 g_object_ref (widget);
2535 empathy_chat_window_remove_chat (old_window, chat);
2536 empathy_chat_window_add_chat (new_window, chat);
2538 g_object_unref (widget);
2539 g_object_unref (chat);
2543 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2546 EmpathyChatWindowPriv *priv;
2549 g_return_if_fail (window != NULL);
2550 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2552 priv = GET_PRIV (window);
2554 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2556 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2561 empathy_chat_window_find_chat (TpAccount *account,
2563 gboolean sms_channel)
2567 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2569 for (l = chat_windows; l; l = l->next) {
2570 EmpathyChatWindowPriv *priv;
2571 EmpathyChatWindow *window;
2575 priv = GET_PRIV (window);
2577 for (ll = priv->chats; ll; ll = ll->next) {
2582 if (account == empathy_chat_get_account (chat) &&
2583 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2584 sms_channel == empathy_chat_is_sms_channel (chat)) {
2594 empathy_chat_window_present_chat (EmpathyChat *chat,
2597 EmpathyChatWindow *window;
2598 EmpathyChatWindowPriv *priv;
2599 guint32 x_timestamp;
2601 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2603 window = chat_window_find_chat (chat);
2605 /* If the chat has no window, create one */
2606 if (window == NULL) {
2607 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2609 window = empathy_chat_window_new ();
2611 /* we want to display the newly created window even if we don't present
2613 priv = GET_PRIV (window);
2614 gtk_widget_show (priv->dialog);
2617 empathy_chat_window_add_chat (window, chat);
2620 /* Don't force the window to show itself when it wasn't
2621 * an action by the user
2623 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2626 priv = GET_PRIV (window);
2628 if (x_timestamp != GDK_CURRENT_TIME) {
2629 /* Don't present or switch tab if the action was earlier than the
2630 * last actions X time, accounting for overflow and the first ever
2633 if (priv->x_user_action_time != 0
2634 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2637 priv->x_user_action_time = x_timestamp;
2640 empathy_chat_window_switch_to_chat (window, chat);
2642 /* Don't use empathy_window_present_with_time () which would move the window
2643 * to our current desktop but move to the window's desktop instead. This is
2644 * more coherent with Shell's 'app is ready' notication which moves the view
2645 * to the app desktop rather than moving the app itself. */
2646 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2648 gtk_widget_grab_focus (chat->input_text_view);
2652 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2656 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2658 guint _nb_rooms = 0, _nb_private = 0;
2660 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2661 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2667 if (nb_rooms != NULL)
2668 *nb_rooms = _nb_rooms;
2669 if (nb_private != NULL)
2670 *nb_private = _nb_private;