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 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
932 gtk_label_set_text (GTK_LABEL (widget), name);
933 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
934 gtk_label_set_text (GTK_LABEL (widget), name);
936 /* Update the window if it's the current chat */
937 if (priv->current_chat == chat) {
938 chat_window_update (window, update_contact_menu);
945 chat_window_update_chat_tab (EmpathyChat *chat)
947 chat_window_update_chat_tab_full (chat, TRUE);
951 chat_window_chat_notify_cb (EmpathyChat *chat)
953 EmpathyChatWindow *window;
954 EmpathyContact *old_remote_contact;
955 EmpathyContact *remote_contact = NULL;
957 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
958 remote_contact = empathy_chat_get_remote_contact (chat);
960 if (old_remote_contact != remote_contact) {
961 /* The remote-contact associated with the chat changed, we need
962 * to keep track of any change of that contact and update the
963 * window each time. */
964 if (remote_contact) {
965 g_signal_connect_swapped (remote_contact, "notify",
966 G_CALLBACK (chat_window_update_chat_tab),
969 if (old_remote_contact) {
970 g_signal_handlers_disconnect_by_func (old_remote_contact,
971 chat_window_update_chat_tab,
975 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
976 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
979 chat_window_update_chat_tab (chat);
981 window = chat_window_find_chat (chat);
982 if (window != NULL) {
983 chat_window_update (window, FALSE);
988 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
989 EmpathySmiley *smiley,
992 EmpathyChatWindowPriv *priv = GET_PRIV (window);
994 GtkTextBuffer *buffer;
997 chat = priv->current_chat;
999 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1000 gtk_text_buffer_get_end_iter (buffer, &iter);
1001 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1005 chat_window_conv_activate_cb (GtkAction *action,
1006 EmpathyChatWindow *window)
1008 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1011 EmpathyContact *remote_contact = NULL;
1013 /* Favorite room menu */
1014 is_room = empathy_chat_is_room (priv->current_chat);
1018 gboolean found = FALSE;
1019 EmpathyChatroom *chatroom;
1021 room = empathy_chat_get_id (priv->current_chat);
1022 account = empathy_chat_get_account (priv->current_chat);
1023 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1025 if (chatroom != NULL)
1026 found = empathy_chatroom_is_favorite (chatroom);
1028 DEBUG ("This room %s favorite", found ? "is" : "is not");
1029 gtk_toggle_action_set_active (
1030 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1032 if (chatroom != NULL)
1033 found = empathy_chatroom_is_always_urgent (chatroom);
1035 gtk_toggle_action_set_active (
1036 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1039 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1040 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1042 /* Show contacts menu */
1043 g_object_get (priv->current_chat,
1044 "remote-contact", &remote_contact,
1045 "show-contacts", &active,
1047 if (remote_contact == NULL) {
1048 gtk_toggle_action_set_active (
1049 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1052 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1053 (remote_contact == NULL));
1054 if (remote_contact != NULL) {
1055 g_object_unref (remote_contact);
1060 chat_window_clear_activate_cb (GtkAction *action,
1061 EmpathyChatWindow *window)
1063 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1065 empathy_chat_clear (priv->current_chat);
1069 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1070 EmpathyChatWindow *window)
1072 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1077 EmpathyChatroom *chatroom;
1079 active = gtk_toggle_action_get_active (toggle_action);
1080 account = empathy_chat_get_account (priv->current_chat);
1081 room = empathy_chat_get_id (priv->current_chat);
1082 name = empathy_chat_dup_name (priv->current_chat);
1084 chatroom = empathy_chatroom_manager_ensure_chatroom (
1085 priv->chatroom_manager,
1090 empathy_chatroom_set_favorite (chatroom, active);
1091 g_object_unref (chatroom);
1096 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1097 EmpathyChatWindow *window)
1099 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1104 EmpathyChatroom *chatroom;
1106 active = gtk_toggle_action_get_active (toggle_action);
1107 account = empathy_chat_get_account (priv->current_chat);
1108 room = empathy_chat_get_id (priv->current_chat);
1109 name = empathy_chat_dup_name (priv->current_chat);
1111 chatroom = empathy_chatroom_manager_ensure_chatroom (
1112 priv->chatroom_manager,
1117 empathy_chatroom_set_always_urgent (chatroom, active);
1118 g_object_unref (chatroom);
1123 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1124 EmpathyChatWindow *window)
1126 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1129 active = gtk_toggle_action_get_active (toggle_action);
1131 empathy_chat_set_show_contacts (priv->current_chat, active);
1135 chat_window_invite_participant_activate_cb (GtkAction *action,
1136 EmpathyChatWindow *window)
1138 EmpathyChatWindowPriv *priv;
1140 EmpathyTpChat *tp_chat;
1143 priv = GET_PRIV (window);
1145 g_return_if_fail (priv->current_chat != NULL);
1147 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1149 dialog = empathy_invite_participant_dialog_new (
1150 GTK_WINDOW (priv->dialog), tp_chat);
1151 gtk_widget_show (dialog);
1153 response = gtk_dialog_run (GTK_DIALOG (dialog));
1155 if (response == GTK_RESPONSE_ACCEPT) {
1156 TpContact *tp_contact;
1157 EmpathyContact *contact;
1159 tp_contact = empathy_invite_participant_dialog_get_selected (
1160 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1161 if (tp_contact == NULL) goto out;
1163 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1165 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1166 contact, _("Inviting you to this room"));
1168 g_object_unref (contact);
1172 gtk_widget_destroy (dialog);
1176 chat_window_close_activate_cb (GtkAction *action,
1177 EmpathyChatWindow *window)
1179 EmpathyChatWindowPriv *priv;
1181 priv = GET_PRIV (window);
1183 g_return_if_fail (priv->current_chat != NULL);
1185 maybe_close_chat (window, priv->current_chat);
1189 chat_window_edit_activate_cb (GtkAction *action,
1190 EmpathyChatWindow *window)
1192 EmpathyChatWindowPriv *priv;
1193 GtkClipboard *clipboard;
1194 GtkTextBuffer *buffer;
1195 gboolean text_available;
1197 priv = GET_PRIV (window);
1199 g_return_if_fail (priv->current_chat != NULL);
1201 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1202 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1203 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1204 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1208 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1209 if (gtk_text_buffer_get_has_selection (buffer)) {
1210 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1211 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1215 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1217 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1218 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1221 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1222 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1223 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1227 chat_window_cut_activate_cb (GtkAction *action,
1228 EmpathyChatWindow *window)
1230 EmpathyChatWindowPriv *priv;
1232 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1234 priv = GET_PRIV (window);
1236 empathy_chat_cut (priv->current_chat);
1240 chat_window_copy_activate_cb (GtkAction *action,
1241 EmpathyChatWindow *window)
1243 EmpathyChatWindowPriv *priv;
1245 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1247 priv = GET_PRIV (window);
1249 empathy_chat_copy (priv->current_chat);
1253 chat_window_paste_activate_cb (GtkAction *action,
1254 EmpathyChatWindow *window)
1256 EmpathyChatWindowPriv *priv;
1258 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1260 priv = GET_PRIV (window);
1262 empathy_chat_paste (priv->current_chat);
1266 chat_window_find_activate_cb (GtkAction *action,
1267 EmpathyChatWindow *window)
1269 EmpathyChatWindowPriv *priv;
1271 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1273 priv = GET_PRIV (window);
1275 empathy_chat_find (priv->current_chat);
1279 chat_window_tabs_next_activate_cb (GtkAction *action,
1280 EmpathyChatWindow *window)
1282 EmpathyChatWindowPriv *priv;
1283 gint index_, numPages;
1284 gboolean wrap_around;
1286 priv = GET_PRIV (window);
1288 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1289 &wrap_around, NULL);
1291 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1292 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1294 if (index_ == (numPages - 1) && wrap_around) {
1295 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1299 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1303 chat_window_tabs_previous_activate_cb (GtkAction *action,
1304 EmpathyChatWindow *window)
1306 EmpathyChatWindowPriv *priv;
1307 gint index_, numPages;
1308 gboolean wrap_around;
1310 priv = GET_PRIV (window);
1312 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1313 &wrap_around, NULL);
1315 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1316 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1318 if (index_ <= 0 && wrap_around) {
1319 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1323 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1327 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1328 EmpathyChatWindow *window)
1330 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1331 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1332 empathy_get_current_action_time ());
1336 chat_window_tabs_left_activate_cb (GtkAction *action,
1337 EmpathyChatWindow *window)
1339 EmpathyChatWindowPriv *priv;
1341 gint index_, num_pages;
1343 priv = GET_PRIV (window);
1345 chat = priv->current_chat;
1346 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1351 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1355 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1356 chat_window_menu_context_update (priv, num_pages);
1360 chat_window_tabs_right_activate_cb (GtkAction *action,
1361 EmpathyChatWindow *window)
1363 EmpathyChatWindowPriv *priv;
1365 gint index_, num_pages;
1367 priv = GET_PRIV (window);
1369 chat = priv->current_chat;
1370 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1372 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1376 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1377 chat_window_menu_context_update (priv, num_pages);
1380 static EmpathyChatWindow *
1381 empathy_chat_window_new (void)
1383 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1387 chat_window_detach_activate_cb (GtkAction *action,
1388 EmpathyChatWindow *window)
1390 EmpathyChatWindowPriv *priv;
1391 EmpathyChatWindow *new_window;
1394 priv = GET_PRIV (window);
1396 chat = priv->current_chat;
1397 new_window = empathy_chat_window_new ();
1399 empathy_chat_window_move_chat (window, new_window, chat);
1401 priv = GET_PRIV (new_window);
1402 gtk_widget_show (priv->dialog);
1406 chat_window_help_contents_activate_cb (GtkAction *action,
1407 EmpathyChatWindow *window)
1409 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1411 empathy_url_show (priv->dialog, "ghelp:empathy");
1415 chat_window_help_about_activate_cb (GtkAction *action,
1416 EmpathyChatWindow *window)
1418 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1420 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1424 chat_window_delete_event_cb (GtkWidget *dialog,
1426 EmpathyChatWindow *window)
1428 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1429 EmpathyChat *chat = NULL;
1433 DEBUG ("Delete event received");
1435 for (l = priv->chats; l != NULL; l = l->next) {
1436 if (chat_needs_close_confirmation (l->data)) {
1443 confirm_close (window, TRUE, n_rooms,
1444 (n_rooms == 1 ? chat : NULL));
1446 remove_all_chats (window);
1453 chat_window_composing_cb (EmpathyChat *chat,
1454 gboolean is_composing,
1455 EmpathyChatWindow *window)
1457 chat_window_update_chat_tab (chat);
1461 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1464 EmpathyChatWindowPriv *priv;
1466 priv = GET_PRIV (window);
1468 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1472 chat_window_notification_closed_cb (NotifyNotification *notify,
1473 EmpathyChatWindow *self)
1475 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1477 g_object_unref (notify);
1478 if (priv->notification == notify) {
1479 priv->notification = NULL;
1484 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1485 EmpathyMessage *message,
1488 EmpathyContact *sender;
1489 const gchar *header;
1493 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1494 gboolean res, has_x_canonical_append;
1495 NotifyNotification *notification = priv->notification;
1497 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1500 res = g_settings_get_boolean (priv->gsettings_notif,
1501 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1508 sender = empathy_message_get_sender (message);
1509 header = empathy_contact_get_alias (sender);
1510 body = empathy_message_get_body (message);
1511 escaped = g_markup_escape_text (body, -1);
1512 has_x_canonical_append = empathy_notify_manager_has_capability (
1513 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1515 if (notification != NULL && !has_x_canonical_append) {
1516 /* if the notification server supports x-canonical-append, it is
1517 better to not use notify_notification_update to avoid
1518 overwriting the current notification message */
1519 notify_notification_update (notification,
1520 header, escaped, NULL);
1522 /* if the notification server supports x-canonical-append,
1523 the hint will be added, so that the message from the
1524 just created notification will be automatically appended
1525 to an existing notification with the same title.
1526 In this way the previous message will not be lost: the new
1527 message will appear below it, in the same notification */
1528 notification = notify_notification_new (header, escaped, NULL);
1530 if (priv->notification == NULL) {
1531 priv->notification = notification;
1534 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1536 tp_g_signal_connect_object (notification, "closed",
1537 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1539 if (has_x_canonical_append) {
1540 /* We have to set a not empty string to keep libnotify happy */
1541 notify_notification_set_hint_string (notification,
1542 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1545 notify_notification_set_hint (notification,
1546 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1547 g_variant_new_string ("im.received"));
1550 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1551 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1553 if (pixbuf != NULL) {
1554 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1555 g_object_unref (pixbuf);
1558 notify_notification_show (notification, NULL);
1564 chat_window_set_highlight_room_labels (EmpathyChat *chat)
1566 gchar *markup, *name;
1569 if (!empathy_chat_is_room (chat))
1572 name = empathy_chat_dup_name (chat);
1573 markup = g_markup_printf_escaped (
1574 "<span color=\"red\" weight=\"bold\">%s</span>",
1577 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1578 gtk_label_set_markup (GTK_LABEL (widget), markup);
1580 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1581 gtk_label_set_markup (GTK_LABEL (widget), markup);
1588 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1590 EmpathyChatWindowPriv *priv;
1593 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1595 priv = GET_PRIV (window);
1597 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1603 chat_window_new_message_cb (EmpathyChat *chat,
1604 EmpathyMessage *message,
1606 gboolean should_highlight,
1607 EmpathyChatWindow *window)
1609 EmpathyChatWindowPriv *priv;
1611 gboolean needs_urgency;
1612 EmpathyContact *sender;
1614 priv = GET_PRIV (window);
1616 has_focus = empathy_chat_window_has_focus (window);
1618 /* - if we're the sender, we play the sound if it's specified in the
1619 * preferences and we're not away.
1620 * - if we receive a message, we play the sound if it's specified in the
1621 * preferences and the window does not have focus on the chat receiving
1625 sender = empathy_message_get_sender (message);
1627 if (empathy_contact_is_user (sender)) {
1628 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1629 EMPATHY_SOUND_MESSAGE_OUTGOING);
1632 if (has_focus && priv->current_chat == chat) {
1633 /* window and tab are focused so consider the message to be read */
1635 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1636 empathy_chat_messages_read (chat);
1640 /* Update the chat tab if this is the first unread message */
1641 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1642 chat_window_update_chat_tab (chat);
1645 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1646 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1647 * an unamed MUC (msn-like).
1648 * In case of a MUC, we set urgency if either:
1649 * a) the chatroom's always_urgent property is TRUE
1650 * b) the message contains our alias
1652 if (empathy_chat_is_room (chat)) {
1655 EmpathyChatroom *chatroom;
1657 account = empathy_chat_get_account (chat);
1658 room = empathy_chat_get_id (chat);
1660 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1663 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1664 needs_urgency = TRUE;
1666 needs_urgency = should_highlight;
1669 needs_urgency = TRUE;
1672 if (needs_urgency) {
1673 chat_window_set_highlight_room_labels (chat);
1676 chat_window_set_urgency_hint (window, TRUE);
1679 /* Pending messages have already been displayed and notified in the
1680 * approver, so we don't display a notification and play a sound for those */
1682 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1683 EMPATHY_SOUND_MESSAGE_INCOMING);
1685 chat_window_show_or_update_notification (window, message, chat);
1689 /* update the number of unread messages and the window icon */
1690 chat_window_title_update (priv);
1691 chat_window_icon_update (priv, TRUE);
1695 chat_window_command_part (EmpathyChat *chat,
1698 EmpathyChat *chat_to_be_parted;
1699 EmpathyTpChat *tp_chat = NULL;
1701 if (strv[1] == NULL) {
1702 /* No chatroom ID specified */
1703 tp_chat = empathy_chat_get_tp_chat (chat);
1705 empathy_tp_chat_leave (tp_chat, "");
1708 chat_to_be_parted = empathy_chat_window_find_chat (
1709 empathy_chat_get_account (chat), strv[1], FALSE);
1711 if (chat_to_be_parted != NULL) {
1712 /* Found a chatroom matching the specified ID */
1713 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1715 empathy_tp_chat_leave (tp_chat, strv[2]);
1719 /* Going by the syntax of PART command:
1721 * /PART [<chatroom-ID>] [<reason>]
1723 * Chatroom-ID is not a must to specify a reason.
1724 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1725 * MUC then the current chatroom should be parted and srtv[1] should
1726 * be treated as part of the optional part-message. */
1727 message = g_strconcat (strv[1], " ", strv[2], NULL);
1728 tp_chat = empathy_chat_get_tp_chat (chat);
1730 empathy_tp_chat_leave (tp_chat, message);
1736 static GtkNotebook *
1737 notebook_create_window_cb (GtkNotebook *source,
1743 EmpathyChatWindowPriv *priv;
1744 EmpathyChatWindow *window, *new_window;
1747 chat = EMPATHY_CHAT (page);
1748 window = chat_window_find_chat (chat);
1750 new_window = empathy_chat_window_new ();
1751 priv = GET_PRIV (new_window);
1753 DEBUG ("Detach hook called");
1755 empathy_chat_window_move_chat (window, new_window, chat);
1757 gtk_widget_show (priv->dialog);
1758 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1764 chat_window_page_switched_cb (GtkNotebook *notebook,
1767 EmpathyChatWindow *window)
1769 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1770 EmpathyChat *chat = EMPATHY_CHAT (child);
1772 DEBUG ("Page switched");
1774 if (priv->page_added) {
1775 priv->page_added = FALSE;
1776 empathy_chat_scroll_down (chat);
1778 else if (priv->current_chat == chat) {
1782 priv->current_chat = chat;
1783 empathy_chat_messages_read (chat);
1785 chat_window_update_chat_tab (chat);
1789 chat_window_page_added_cb (GtkNotebook *notebook,
1792 EmpathyChatWindow *window)
1794 EmpathyChatWindowPriv *priv;
1797 priv = GET_PRIV (window);
1799 /* If we just received DND to the same window, we don't want
1800 * to do anything here like removing the tab and then readding
1801 * it, so we return here and in "page-added".
1803 if (priv->dnd_same_window) {
1804 DEBUG ("Page added (back to the same window)");
1805 priv->dnd_same_window = FALSE;
1809 DEBUG ("Page added");
1811 /* Get chat object */
1812 chat = EMPATHY_CHAT (child);
1814 /* Connect chat signals for this window */
1815 g_signal_connect (chat, "composing",
1816 G_CALLBACK (chat_window_composing_cb),
1818 g_signal_connect (chat, "new-message",
1819 G_CALLBACK (chat_window_new_message_cb),
1821 g_signal_connect (chat, "part-command-entered",
1822 G_CALLBACK (chat_window_command_part),
1824 g_signal_connect (chat, "notify::tp-chat",
1825 G_CALLBACK (chat_window_update_chat_tab),
1828 /* Set flag so we know to perform some special operations on
1829 * switch page due to the new page being added.
1831 priv->page_added = TRUE;
1833 /* Get list of chats up to date */
1834 priv->chats = g_list_append (priv->chats, chat);
1836 chat_window_update_chat_tab (chat);
1840 chat_window_page_removed_cb (GtkNotebook *notebook,
1843 EmpathyChatWindow *window)
1845 EmpathyChatWindowPriv *priv;
1848 priv = GET_PRIV (window);
1850 /* If we just received DND to the same window, we don't want
1851 * to do anything here like removing the tab and then readding
1852 * it, so we return here and in "page-added".
1854 if (priv->dnd_same_window) {
1855 DEBUG ("Page removed (and will be readded to same window)");
1859 DEBUG ("Page removed");
1861 /* Get chat object */
1862 chat = EMPATHY_CHAT (child);
1864 /* Disconnect all signal handlers for this chat and this window */
1865 g_signal_handlers_disconnect_by_func (chat,
1866 G_CALLBACK (chat_window_composing_cb),
1868 g_signal_handlers_disconnect_by_func (chat,
1869 G_CALLBACK (chat_window_new_message_cb),
1871 g_signal_handlers_disconnect_by_func (chat,
1872 G_CALLBACK (chat_window_update_chat_tab),
1875 /* Keep list of chats up to date */
1876 priv->chats = g_list_remove (priv->chats, chat);
1877 empathy_chat_messages_read (chat);
1879 if (priv->chats == NULL) {
1880 g_object_unref (window);
1882 chat_window_update (window, TRUE);
1887 chat_window_focus_in_event_cb (GtkWidget *widget,
1889 EmpathyChatWindow *window)
1891 EmpathyChatWindowPriv *priv;
1893 priv = GET_PRIV (window);
1895 empathy_chat_messages_read (priv->current_chat);
1897 chat_window_set_urgency_hint (window, FALSE);
1899 /* Update the title, since we now mark all unread messages as read. */
1900 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1906 chat_window_drag_drop (GtkWidget *widget,
1907 GdkDragContext *context,
1911 EmpathyChatWindow *window)
1914 EmpathyChatWindowPriv *priv;
1916 priv = GET_PRIV (window);
1918 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1919 if (target == GDK_NONE)
1920 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1922 if (target != GDK_NONE) {
1923 gtk_drag_get_data (widget, context, target, time_);
1931 chat_window_drag_motion (GtkWidget *widget,
1932 GdkDragContext *context,
1936 EmpathyChatWindow *window)
1939 EmpathyChatWindowPriv *priv;
1941 priv = GET_PRIV (window);
1943 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1944 if (target != GDK_NONE) {
1945 /* This is a file drag. Ensure the contact is online and set the
1946 drag type to COPY. Note that it's possible that the tab will
1947 be switched by GTK+ after a timeout from drag_motion without
1948 getting another drag_motion to disable the drop. You have
1949 to hold your mouse really still.
1951 EmpathyContact *contact;
1953 priv = GET_PRIV (window);
1954 contact = empathy_chat_get_remote_contact (priv->current_chat);
1955 /* contact is NULL for multi-user chats. We don't do
1956 * file transfers to MUCs. We also don't send files
1957 * to offline contacts or contacts that don't support
1960 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1961 gdk_drag_status (context, 0, time_);
1964 if (!(empathy_contact_get_capabilities (contact)
1965 & EMPATHY_CAPABILITIES_FT)) {
1966 gdk_drag_status (context, 0, time_);
1969 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1973 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1974 if (target != GDK_NONE) {
1975 /* This is a drag of a contact from a contact list. Set to COPY.
1976 FIXME: If this drag is to a MUC window, it invites the user.
1977 Otherwise, it opens a chat. Should we use a different drag
1978 type for invites? Should we allow ASK?
1980 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1988 chat_window_drag_data_received (GtkWidget *widget,
1989 GdkDragContext *context,
1992 GtkSelectionData *selection,
1995 EmpathyChatWindow *window)
1997 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1998 EmpathyChat *chat = NULL;
1999 EmpathyChatWindow *old_window;
2000 TpAccount *account = NULL;
2001 EmpathyClientFactory *factory;
2004 const gchar *account_id;
2005 const gchar *contact_id;
2007 id = (const gchar*) gtk_selection_data_get_data (selection);
2009 factory = empathy_client_factory_dup ();
2011 DEBUG ("DND contact from roster with id:'%s'", id);
2013 strv = g_strsplit (id, ":", 2);
2014 if (g_strv_length (strv) == 2) {
2015 account_id = strv[0];
2016 contact_id = strv[1];
2018 tp_simple_client_factory_ensure_account (
2019 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2022 g_object_unref (factory);
2023 if (account != NULL)
2024 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2027 if (account == NULL) {
2029 gtk_drag_finish (context, FALSE, FALSE, time_);
2034 empathy_chat_with_contact_id (
2035 account, contact_id,
2036 empathy_get_current_action_time (),
2044 old_window = chat_window_find_chat (chat);
2046 if (old_window == window) {
2047 gtk_drag_finish (context, TRUE, FALSE, time_);
2051 empathy_chat_window_move_chat (old_window, window, chat);
2053 empathy_chat_window_add_chat (window, chat);
2056 /* Added to take care of any outstanding chat events */
2057 empathy_chat_window_present_chat (chat,
2058 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2060 /* We should return TRUE to remove the data when doing
2061 * GDK_ACTION_MOVE, but we don't here otherwise it has
2062 * weird consequences, and we handle that internally
2063 * anyway with add_chat () and remove_chat ().
2065 gtk_drag_finish (context, TRUE, FALSE, time_);
2067 else if (info == DND_DRAG_TYPE_URI_LIST) {
2068 EmpathyChatWindowPriv *priv;
2069 EmpathyContact *contact;
2072 priv = GET_PRIV (window);
2073 contact = empathy_chat_get_remote_contact (priv->current_chat);
2075 /* contact is NULL when current_chat is a multi-user chat.
2076 * We don't do file transfers to MUCs, so just cancel the drag.
2078 if (contact == NULL) {
2079 gtk_drag_finish (context, TRUE, FALSE, time_);
2083 data = (const gchar *) gtk_selection_data_get_data (selection);
2084 empathy_send_file_from_uri_list (contact, data);
2086 gtk_drag_finish (context, TRUE, FALSE, time_);
2088 else if (info == DND_DRAG_TYPE_TAB) {
2090 EmpathyChatWindow *old_window = NULL;
2094 chat = (void *) gtk_selection_data_get_data (selection);
2095 old_window = chat_window_find_chat (*chat);
2098 EmpathyChatWindowPriv *priv;
2100 priv = GET_PRIV (window);
2101 priv->dnd_same_window = (old_window == window);
2102 DEBUG ("DND tab (within same window: %s)",
2103 priv->dnd_same_window ? "Yes" : "No");
2106 DEBUG ("DND from unknown source");
2107 gtk_drag_finish (context, FALSE, FALSE, time_);
2112 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2113 guint num_chats_in_manager,
2114 EmpathyChatWindow *window)
2116 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2118 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2119 num_chats_in_manager > 0);
2123 chat_window_finalize (GObject *object)
2125 EmpathyChatWindow *window;
2126 EmpathyChatWindowPriv *priv;
2128 window = EMPATHY_CHAT_WINDOW (object);
2129 priv = GET_PRIV (window);
2131 DEBUG ("Finalized: %p", object);
2133 g_object_unref (priv->ui_manager);
2134 g_object_unref (priv->chatroom_manager);
2135 g_object_unref (priv->notify_mgr);
2136 g_object_unref (priv->gsettings_chat);
2137 g_object_unref (priv->gsettings_notif);
2138 g_object_unref (priv->gsettings_ui);
2139 g_object_unref (priv->sound_mgr);
2141 if (priv->notification != NULL) {
2142 notify_notification_close (priv->notification, NULL);
2143 priv->notification = NULL;
2146 if (priv->contact_targets) {
2147 gtk_target_list_unref (priv->contact_targets);
2149 if (priv->file_targets) {
2150 gtk_target_list_unref (priv->file_targets);
2153 if (priv->chat_manager) {
2154 g_signal_handler_disconnect (priv->chat_manager,
2155 priv->chat_manager_chats_changed_id);
2156 g_object_unref (priv->chat_manager);
2157 priv->chat_manager = NULL;
2160 chat_windows = g_list_remove (chat_windows, window);
2161 gtk_widget_destroy (priv->dialog);
2163 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2167 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2169 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2171 object_class->finalize = chat_window_finalize;
2173 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2177 empathy_chat_window_init (EmpathyChatWindow *window)
2180 GtkAccelGroup *accel_group;
2185 GtkWidget *chat_vbox;
2187 EmpathySmileyManager *smiley_manager;
2188 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2189 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2191 window->priv = priv;
2192 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2193 gui = empathy_builder_get_file (filename,
2194 "chat_window", &priv->dialog,
2195 "chat_vbox", &chat_vbox,
2196 "ui_manager", &priv->ui_manager,
2197 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2198 "menu_conv_favorite", &priv->menu_conv_favorite,
2199 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2200 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2201 "menu_edit_cut", &priv->menu_edit_cut,
2202 "menu_edit_copy", &priv->menu_edit_copy,
2203 "menu_edit_paste", &priv->menu_edit_paste,
2204 "menu_edit_find", &priv->menu_edit_find,
2205 "menu_tabs_next", &priv->menu_tabs_next,
2206 "menu_tabs_prev", &priv->menu_tabs_prev,
2207 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2208 "menu_tabs_left", &priv->menu_tabs_left,
2209 "menu_tabs_right", &priv->menu_tabs_right,
2210 "menu_tabs_detach", &priv->menu_tabs_detach,
2214 empathy_builder_connect (gui, window,
2215 "menu_conv", "activate", chat_window_conv_activate_cb,
2216 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2217 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2218 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2219 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2220 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2221 "menu_conv_close", "activate", chat_window_close_activate_cb,
2222 "menu_edit", "activate", chat_window_edit_activate_cb,
2223 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2224 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2225 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2226 "menu_edit_find", "activate", chat_window_find_activate_cb,
2227 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2228 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2229 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2230 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2231 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2232 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2233 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2234 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2237 g_object_ref (priv->ui_manager);
2238 g_object_unref (gui);
2240 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2241 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2242 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2243 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2245 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2247 priv->notebook = gtk_notebook_new ();
2249 g_signal_connect (priv->notebook, "create-window",
2250 G_CALLBACK (notebook_create_window_cb), window);
2252 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2253 "EmpathyChatWindow");
2254 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2255 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2256 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2257 gtk_widget_show (priv->notebook);
2260 accel_group = gtk_accel_group_new ();
2261 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2263 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2264 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2267 gtk_accel_group_connect (accel_group,
2274 g_object_unref (accel_group);
2276 /* Set up drag target lists */
2277 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2278 G_N_ELEMENTS (drag_types_dest_contact));
2279 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2280 G_N_ELEMENTS (drag_types_dest_file));
2282 /* Set up smiley menu */
2283 smiley_manager = empathy_smiley_manager_dup_singleton ();
2284 submenu = empathy_smiley_menu_new (smiley_manager,
2285 chat_window_insert_smiley_activate_cb,
2287 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2288 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2289 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2290 g_object_unref (smiley_manager);
2292 /* Set up signals we can't do with ui file since we may need to
2293 * block/unblock them at some later stage.
2296 g_signal_connect (priv->dialog,
2298 G_CALLBACK (chat_window_delete_event_cb),
2300 g_signal_connect (priv->dialog,
2302 G_CALLBACK (chat_window_focus_in_event_cb),
2304 g_signal_connect_after (priv->notebook,
2306 G_CALLBACK (chat_window_page_switched_cb),
2308 g_signal_connect (priv->notebook,
2310 G_CALLBACK (chat_window_page_added_cb),
2312 g_signal_connect (priv->notebook,
2314 G_CALLBACK (chat_window_page_removed_cb),
2317 /* Set up drag and drop */
2318 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2319 GTK_DEST_DEFAULT_HIGHLIGHT,
2321 G_N_ELEMENTS (drag_types_dest),
2322 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2324 /* connect_after to allow GtkNotebook's built-in tab switching */
2325 g_signal_connect_after (priv->notebook,
2327 G_CALLBACK (chat_window_drag_motion),
2329 g_signal_connect (priv->notebook,
2330 "drag-data-received",
2331 G_CALLBACK (chat_window_drag_data_received),
2333 g_signal_connect (priv->notebook,
2335 G_CALLBACK (chat_window_drag_drop),
2338 chat_windows = g_list_prepend (chat_windows, window);
2340 /* Set up private details */
2342 priv->current_chat = NULL;
2343 priv->notification = NULL;
2345 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2347 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2348 priv->chat_manager_chats_changed_id =
2349 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2350 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2353 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2354 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2358 /* Returns the window to open a new tab in if there is a suitable window,
2359 * otherwise, returns NULL indicating that a new window should be added.
2361 static EmpathyChatWindow *
2362 empathy_chat_window_get_default (gboolean room)
2364 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2366 gboolean separate_windows = TRUE;
2368 separate_windows = g_settings_get_boolean (gsettings,
2369 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2371 g_object_unref (gsettings);
2373 if (separate_windows) {
2374 /* Always create a new window */
2378 for (l = chat_windows; l; l = l->next) {
2379 EmpathyChatWindow *chat_window;
2380 guint nb_rooms, nb_private;
2382 chat_window = l->data;
2384 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2386 /* Skip the window if there aren't any rooms in it */
2387 if (room && nb_rooms == 0)
2390 /* Skip the window if there aren't any 1-1 chats in it */
2391 if (!room && nb_private == 0)
2401 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2404 EmpathyChatWindowPriv *priv;
2406 GtkWidget *popup_label;
2408 GValue value = { 0, };
2410 g_return_if_fail (window != NULL);
2411 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2413 priv = GET_PRIV (window);
2415 /* Reference the chat object */
2416 g_object_ref (chat);
2418 /* If this window has just been created, position it */
2419 if (priv->chats == NULL) {
2420 const gchar *name = "chat-window";
2421 gboolean separate_windows;
2423 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2424 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2426 if (empathy_chat_is_room (chat))
2427 name = "room-window";
2429 if (separate_windows) {
2432 /* Save current position of the window */
2433 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2435 /* First bind to the 'generic' name. So new window for which we didn't
2436 * save a geometry yet will have the geometry of the last saved
2437 * window (bgo #601191). */
2438 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2440 /* Restore previous position of the window so the newly created window
2441 * won't be in the same position as the latest saved window and so
2442 * completely hide it. */
2443 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2445 /* Then bind it to the name of the contact/room so we'll save the
2446 * geometry specific to this window */
2447 name = empathy_chat_get_id (chat);
2450 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2453 child = GTK_WIDGET (chat);
2454 label = chat_window_create_label (window, chat, TRUE);
2455 popup_label = chat_window_create_label (window, chat, FALSE);
2456 gtk_widget_show (child);
2458 g_signal_connect (chat, "notify::name",
2459 G_CALLBACK (chat_window_chat_notify_cb),
2461 g_signal_connect (chat, "notify::subject",
2462 G_CALLBACK (chat_window_chat_notify_cb),
2464 g_signal_connect (chat, "notify::remote-contact",
2465 G_CALLBACK (chat_window_chat_notify_cb),
2467 g_signal_connect (chat, "notify::sms-channel",
2468 G_CALLBACK (chat_window_chat_notify_cb),
2470 g_signal_connect (chat, "notify::n-messages-sending",
2471 G_CALLBACK (chat_window_chat_notify_cb),
2473 g_signal_connect (chat, "notify::nb-unread-messages",
2474 G_CALLBACK (chat_window_chat_notify_cb),
2476 chat_window_chat_notify_cb (chat);
2478 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2479 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2480 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2481 g_value_init (&value, G_TYPE_BOOLEAN);
2482 g_value_set_boolean (&value, TRUE);
2483 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2484 child, "tab-expand" , &value);
2485 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2486 child, "tab-fill" , &value);
2487 g_value_unset (&value);
2489 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2493 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2496 EmpathyChatWindowPriv *priv;
2498 EmpathyContact *remote_contact;
2499 EmpathyChatManager *chat_manager;
2501 g_return_if_fail (window != NULL);
2502 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2504 priv = GET_PRIV (window);
2506 g_signal_handlers_disconnect_by_func (chat,
2507 chat_window_chat_notify_cb,
2509 remote_contact = g_object_get_data (G_OBJECT (chat),
2510 "chat-window-remote-contact");
2511 if (remote_contact) {
2512 g_signal_handlers_disconnect_by_func (remote_contact,
2513 chat_window_update_chat_tab,
2517 chat_manager = empathy_chat_manager_dup_singleton ();
2518 empathy_chat_manager_closed_chat (chat_manager, chat);
2519 g_object_unref (chat_manager);
2521 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2523 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2525 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2527 g_object_unref (chat);
2531 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2532 EmpathyChatWindow *new_window,
2537 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2538 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2539 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2541 widget = GTK_WIDGET (chat);
2543 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2544 G_OBJECT (widget)->ref_count);
2546 /* We reference here to make sure we don't loose the widget
2547 * and the EmpathyChat object during the move.
2549 g_object_ref (chat);
2550 g_object_ref (widget);
2552 empathy_chat_window_remove_chat (old_window, chat);
2553 empathy_chat_window_add_chat (new_window, chat);
2555 g_object_unref (widget);
2556 g_object_unref (chat);
2560 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2563 EmpathyChatWindowPriv *priv;
2566 g_return_if_fail (window != NULL);
2567 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2569 priv = GET_PRIV (window);
2571 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2573 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2578 empathy_chat_window_find_chat (TpAccount *account,
2580 gboolean sms_channel)
2584 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2586 for (l = chat_windows; l; l = l->next) {
2587 EmpathyChatWindowPriv *priv;
2588 EmpathyChatWindow *window;
2592 priv = GET_PRIV (window);
2594 for (ll = priv->chats; ll; ll = ll->next) {
2599 if (account == empathy_chat_get_account (chat) &&
2600 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2601 sms_channel == empathy_chat_is_sms_channel (chat)) {
2611 empathy_chat_window_present_chat (EmpathyChat *chat,
2614 EmpathyChatWindow *window;
2615 EmpathyChatWindowPriv *priv;
2616 guint32 x_timestamp;
2618 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2620 window = chat_window_find_chat (chat);
2622 /* If the chat has no window, create one */
2623 if (window == NULL) {
2624 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2626 window = empathy_chat_window_new ();
2628 /* we want to display the newly created window even if we don't present
2630 priv = GET_PRIV (window);
2631 gtk_widget_show (priv->dialog);
2634 empathy_chat_window_add_chat (window, chat);
2637 /* Don't force the window to show itself when it wasn't
2638 * an action by the user
2640 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2643 priv = GET_PRIV (window);
2645 if (x_timestamp != GDK_CURRENT_TIME) {
2646 /* Don't present or switch tab if the action was earlier than the
2647 * last actions X time, accounting for overflow and the first ever
2650 if (priv->x_user_action_time != 0
2651 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2654 priv->x_user_action_time = x_timestamp;
2657 empathy_chat_window_switch_to_chat (window, chat);
2659 /* Don't use empathy_window_present_with_time () which would move the window
2660 * to our current desktop but move to the window's desktop instead. This is
2661 * more coherent with Shell's 'app is ready' notication which moves the view
2662 * to the app desktop rather than moving the app itself. */
2663 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2665 gtk_widget_grab_focus (chat->input_text_view);
2669 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2673 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2675 guint _nb_rooms = 0, _nb_private = 0;
2677 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2678 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2684 if (nb_rooms != NULL)
2685 *nb_rooms = _nb_rooms;
2686 if (nb_private != NULL)
2687 *nb_private = _nb_private;