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 if (remote_contact != NULL) {
924 const gchar * const *types;
926 types = empathy_contact_get_client_types (remote_contact);
927 if (types != NULL && !tp_strdiff (types[0], "phone")) {
928 /* I'm on a phone ! */
931 name = g_strdup_printf ("☎ %s", name);
936 markup = g_string_free (tooltip, FALSE);
937 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
938 gtk_widget_set_tooltip_markup (widget, markup);
939 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
940 gtk_widget_set_tooltip_markup (widget, markup);
943 /* Update tab and menu label */
944 if (empathy_chat_is_highlighted (chat)) {
945 markup = g_markup_printf_escaped (
946 "<span color=\"red\" weight=\"bold\">%s</span>",
949 markup = g_markup_escape_text (name, -1);
952 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
953 gtk_label_set_markup (GTK_LABEL (widget), markup);
954 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
955 gtk_label_set_markup (GTK_LABEL (widget), markup);
958 /* Update the window if it's the current chat */
959 if (priv->current_chat == chat) {
960 chat_window_update (window, update_contact_menu);
967 chat_window_update_chat_tab (EmpathyChat *chat)
969 chat_window_update_chat_tab_full (chat, TRUE);
973 chat_window_chat_notify_cb (EmpathyChat *chat)
975 EmpathyChatWindow *window;
976 EmpathyContact *old_remote_contact;
977 EmpathyContact *remote_contact = NULL;
979 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
980 remote_contact = empathy_chat_get_remote_contact (chat);
982 if (old_remote_contact != remote_contact) {
983 /* The remote-contact associated with the chat changed, we need
984 * to keep track of any change of that contact and update the
985 * window each time. */
986 if (remote_contact) {
987 g_signal_connect_swapped (remote_contact, "notify",
988 G_CALLBACK (chat_window_update_chat_tab),
991 if (old_remote_contact) {
992 g_signal_handlers_disconnect_by_func (old_remote_contact,
993 chat_window_update_chat_tab,
997 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
998 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1001 chat_window_update_chat_tab (chat);
1003 window = chat_window_find_chat (chat);
1004 if (window != NULL) {
1005 chat_window_update (window, FALSE);
1010 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1011 EmpathySmiley *smiley,
1014 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1016 GtkTextBuffer *buffer;
1019 chat = priv->current_chat;
1021 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1022 gtk_text_buffer_get_end_iter (buffer, &iter);
1023 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1027 chat_window_conv_activate_cb (GtkAction *action,
1028 EmpathyChatWindow *window)
1030 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1033 EmpathyContact *remote_contact = NULL;
1035 /* Favorite room menu */
1036 is_room = empathy_chat_is_room (priv->current_chat);
1040 gboolean found = FALSE;
1041 EmpathyChatroom *chatroom;
1043 room = empathy_chat_get_id (priv->current_chat);
1044 account = empathy_chat_get_account (priv->current_chat);
1045 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1047 if (chatroom != NULL)
1048 found = empathy_chatroom_is_favorite (chatroom);
1050 DEBUG ("This room %s favorite", found ? "is" : "is not");
1051 gtk_toggle_action_set_active (
1052 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1054 if (chatroom != NULL)
1055 found = empathy_chatroom_is_always_urgent (chatroom);
1057 gtk_toggle_action_set_active (
1058 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1061 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1062 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1064 /* Show contacts menu */
1065 g_object_get (priv->current_chat,
1066 "remote-contact", &remote_contact,
1067 "show-contacts", &active,
1069 if (remote_contact == NULL) {
1070 gtk_toggle_action_set_active (
1071 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1074 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1075 (remote_contact == NULL));
1076 if (remote_contact != NULL) {
1077 g_object_unref (remote_contact);
1082 chat_window_clear_activate_cb (GtkAction *action,
1083 EmpathyChatWindow *window)
1085 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1087 empathy_chat_clear (priv->current_chat);
1091 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1092 EmpathyChatWindow *window)
1094 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1099 EmpathyChatroom *chatroom;
1101 active = gtk_toggle_action_get_active (toggle_action);
1102 account = empathy_chat_get_account (priv->current_chat);
1103 room = empathy_chat_get_id (priv->current_chat);
1104 name = empathy_chat_dup_name (priv->current_chat);
1106 chatroom = empathy_chatroom_manager_ensure_chatroom (
1107 priv->chatroom_manager,
1112 empathy_chatroom_set_favorite (chatroom, active);
1113 g_object_unref (chatroom);
1118 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1119 EmpathyChatWindow *window)
1121 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1126 EmpathyChatroom *chatroom;
1128 active = gtk_toggle_action_get_active (toggle_action);
1129 account = empathy_chat_get_account (priv->current_chat);
1130 room = empathy_chat_get_id (priv->current_chat);
1131 name = empathy_chat_dup_name (priv->current_chat);
1133 chatroom = empathy_chatroom_manager_ensure_chatroom (
1134 priv->chatroom_manager,
1139 empathy_chatroom_set_always_urgent (chatroom, active);
1140 g_object_unref (chatroom);
1145 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1146 EmpathyChatWindow *window)
1148 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1151 active = gtk_toggle_action_get_active (toggle_action);
1153 empathy_chat_set_show_contacts (priv->current_chat, active);
1157 chat_window_invite_participant_activate_cb (GtkAction *action,
1158 EmpathyChatWindow *window)
1160 EmpathyChatWindowPriv *priv;
1162 EmpathyTpChat *tp_chat;
1165 priv = GET_PRIV (window);
1167 g_return_if_fail (priv->current_chat != NULL);
1169 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1171 dialog = empathy_invite_participant_dialog_new (
1172 GTK_WINDOW (priv->dialog), tp_chat);
1173 gtk_widget_show (dialog);
1175 response = gtk_dialog_run (GTK_DIALOG (dialog));
1177 if (response == GTK_RESPONSE_ACCEPT) {
1178 TpContact *tp_contact;
1179 EmpathyContact *contact;
1181 tp_contact = empathy_invite_participant_dialog_get_selected (
1182 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1183 if (tp_contact == NULL) goto out;
1185 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1187 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1188 contact, _("Inviting you to this room"));
1190 g_object_unref (contact);
1194 gtk_widget_destroy (dialog);
1198 chat_window_close_activate_cb (GtkAction *action,
1199 EmpathyChatWindow *window)
1201 EmpathyChatWindowPriv *priv;
1203 priv = GET_PRIV (window);
1205 g_return_if_fail (priv->current_chat != NULL);
1207 maybe_close_chat (window, priv->current_chat);
1211 chat_window_edit_activate_cb (GtkAction *action,
1212 EmpathyChatWindow *window)
1214 EmpathyChatWindowPriv *priv;
1215 GtkClipboard *clipboard;
1216 GtkTextBuffer *buffer;
1217 gboolean text_available;
1219 priv = GET_PRIV (window);
1221 g_return_if_fail (priv->current_chat != NULL);
1223 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1224 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1225 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1226 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1230 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1231 if (gtk_text_buffer_get_has_selection (buffer)) {
1232 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1233 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1237 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1239 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1240 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1243 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1244 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1245 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1249 chat_window_cut_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_cut (priv->current_chat);
1262 chat_window_copy_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_copy (priv->current_chat);
1275 chat_window_paste_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_paste (priv->current_chat);
1288 chat_window_find_activate_cb (GtkAction *action,
1289 EmpathyChatWindow *window)
1291 EmpathyChatWindowPriv *priv;
1293 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1295 priv = GET_PRIV (window);
1297 empathy_chat_find (priv->current_chat);
1301 chat_window_tabs_next_activate_cb (GtkAction *action,
1302 EmpathyChatWindow *window)
1304 EmpathyChatWindowPriv *priv;
1305 gint index_, numPages;
1306 gboolean wrap_around;
1308 priv = GET_PRIV (window);
1310 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1311 &wrap_around, NULL);
1313 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1314 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1316 if (index_ == (numPages - 1) && wrap_around) {
1317 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1321 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1325 chat_window_tabs_previous_activate_cb (GtkAction *action,
1326 EmpathyChatWindow *window)
1328 EmpathyChatWindowPriv *priv;
1329 gint index_, numPages;
1330 gboolean wrap_around;
1332 priv = GET_PRIV (window);
1334 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1335 &wrap_around, NULL);
1337 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1338 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1340 if (index_ <= 0 && wrap_around) {
1341 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1345 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1349 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1350 EmpathyChatWindow *window)
1352 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1353 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1354 empathy_get_current_action_time ());
1358 chat_window_tabs_left_activate_cb (GtkAction *action,
1359 EmpathyChatWindow *window)
1361 EmpathyChatWindowPriv *priv;
1363 gint index_, num_pages;
1365 priv = GET_PRIV (window);
1367 chat = priv->current_chat;
1368 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1373 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1377 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1378 chat_window_menu_context_update (priv, num_pages);
1382 chat_window_tabs_right_activate_cb (GtkAction *action,
1383 EmpathyChatWindow *window)
1385 EmpathyChatWindowPriv *priv;
1387 gint index_, num_pages;
1389 priv = GET_PRIV (window);
1391 chat = priv->current_chat;
1392 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1394 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1398 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1399 chat_window_menu_context_update (priv, num_pages);
1402 static EmpathyChatWindow *
1403 empathy_chat_window_new (void)
1405 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1409 chat_window_detach_activate_cb (GtkAction *action,
1410 EmpathyChatWindow *window)
1412 EmpathyChatWindowPriv *priv;
1413 EmpathyChatWindow *new_window;
1416 priv = GET_PRIV (window);
1418 chat = priv->current_chat;
1419 new_window = empathy_chat_window_new ();
1421 empathy_chat_window_move_chat (window, new_window, chat);
1423 priv = GET_PRIV (new_window);
1424 gtk_widget_show (priv->dialog);
1428 chat_window_help_contents_activate_cb (GtkAction *action,
1429 EmpathyChatWindow *window)
1431 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1433 empathy_url_show (priv->dialog, "ghelp:empathy");
1437 chat_window_help_about_activate_cb (GtkAction *action,
1438 EmpathyChatWindow *window)
1440 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1442 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1446 chat_window_delete_event_cb (GtkWidget *dialog,
1448 EmpathyChatWindow *window)
1450 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1451 EmpathyChat *chat = NULL;
1455 DEBUG ("Delete event received");
1457 for (l = priv->chats; l != NULL; l = l->next) {
1458 if (chat_needs_close_confirmation (l->data)) {
1465 confirm_close (window, TRUE, n_rooms,
1466 (n_rooms == 1 ? chat : NULL));
1468 remove_all_chats (window);
1475 chat_window_composing_cb (EmpathyChat *chat,
1476 gboolean is_composing,
1477 EmpathyChatWindow *window)
1479 chat_window_update_chat_tab (chat);
1483 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1486 EmpathyChatWindowPriv *priv;
1488 priv = GET_PRIV (window);
1490 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1494 chat_window_notification_closed_cb (NotifyNotification *notify,
1495 EmpathyChatWindow *self)
1497 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1499 g_object_unref (notify);
1500 if (priv->notification == notify) {
1501 priv->notification = NULL;
1506 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1507 EmpathyMessage *message,
1510 EmpathyContact *sender;
1511 const gchar *header;
1515 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1516 gboolean res, has_x_canonical_append;
1517 NotifyNotification *notification = priv->notification;
1519 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1522 res = g_settings_get_boolean (priv->gsettings_notif,
1523 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1530 sender = empathy_message_get_sender (message);
1531 header = empathy_contact_get_alias (sender);
1532 body = empathy_message_get_body (message);
1533 escaped = g_markup_escape_text (body, -1);
1534 has_x_canonical_append = empathy_notify_manager_has_capability (
1535 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1537 if (notification != NULL && !has_x_canonical_append) {
1538 /* if the notification server supports x-canonical-append, it is
1539 better to not use notify_notification_update to avoid
1540 overwriting the current notification message */
1541 notify_notification_update (notification,
1542 header, escaped, NULL);
1544 /* if the notification server supports x-canonical-append,
1545 the hint will be added, so that the message from the
1546 just created notification will be automatically appended
1547 to an existing notification with the same title.
1548 In this way the previous message will not be lost: the new
1549 message will appear below it, in the same notification */
1550 notification = notify_notification_new (header, escaped, NULL);
1552 if (priv->notification == NULL) {
1553 priv->notification = notification;
1556 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1558 tp_g_signal_connect_object (notification, "closed",
1559 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1561 if (has_x_canonical_append) {
1562 /* We have to set a not empty string to keep libnotify happy */
1563 notify_notification_set_hint_string (notification,
1564 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1567 notify_notification_set_hint (notification,
1568 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1569 g_variant_new_string ("im.received"));
1572 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1573 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1575 if (pixbuf != NULL) {
1576 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1577 g_object_unref (pixbuf);
1580 notify_notification_show (notification, NULL);
1586 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1588 EmpathyChatWindowPriv *priv;
1591 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1593 priv = GET_PRIV (window);
1595 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1601 chat_window_new_message_cb (EmpathyChat *chat,
1602 EmpathyMessage *message,
1604 gboolean should_highlight,
1605 EmpathyChatWindow *window)
1607 EmpathyChatWindowPriv *priv;
1609 gboolean needs_urgency;
1610 EmpathyContact *sender;
1612 priv = GET_PRIV (window);
1614 has_focus = empathy_chat_window_has_focus (window);
1616 /* - if we're the sender, we play the sound if it's specified in the
1617 * preferences and we're not away.
1618 * - if we receive a message, we play the sound if it's specified in the
1619 * preferences and the window does not have focus on the chat receiving
1623 sender = empathy_message_get_sender (message);
1625 if (empathy_contact_is_user (sender)) {
1626 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1627 EMPATHY_SOUND_MESSAGE_OUTGOING);
1630 if (has_focus && priv->current_chat == chat) {
1631 /* window and tab are focused so consider the message to be read */
1633 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1634 empathy_chat_messages_read (chat);
1638 /* Update the chat tab if this is the first unread message */
1639 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1640 chat_window_update_chat_tab (chat);
1643 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1644 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1645 * an unamed MUC (msn-like).
1646 * In case of a MUC, we set urgency if either:
1647 * a) the chatroom's always_urgent property is TRUE
1648 * b) the message contains our alias
1650 if (empathy_chat_is_room (chat)) {
1653 EmpathyChatroom *chatroom;
1655 account = empathy_chat_get_account (chat);
1656 room = empathy_chat_get_id (chat);
1658 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1661 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1662 needs_urgency = TRUE;
1664 needs_urgency = should_highlight;
1667 needs_urgency = TRUE;
1670 if (needs_urgency) {
1672 chat_window_set_urgency_hint (window, TRUE);
1675 /* Pending messages have already been displayed and notified in the
1676 * approver, so we don't display a notification and play a sound for those */
1678 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1679 EMPATHY_SOUND_MESSAGE_INCOMING);
1681 chat_window_show_or_update_notification (window, message, chat);
1685 /* update the number of unread messages and the window icon */
1686 chat_window_title_update (priv);
1687 chat_window_icon_update (priv, TRUE);
1691 chat_window_command_part (EmpathyChat *chat,
1694 EmpathyChat *chat_to_be_parted;
1695 EmpathyTpChat *tp_chat = NULL;
1697 if (strv[1] == NULL) {
1698 /* No chatroom ID specified */
1699 tp_chat = empathy_chat_get_tp_chat (chat);
1701 empathy_tp_chat_leave (tp_chat, "");
1704 chat_to_be_parted = empathy_chat_window_find_chat (
1705 empathy_chat_get_account (chat), strv[1], FALSE);
1707 if (chat_to_be_parted != NULL) {
1708 /* Found a chatroom matching the specified ID */
1709 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1711 empathy_tp_chat_leave (tp_chat, strv[2]);
1715 /* Going by the syntax of PART command:
1717 * /PART [<chatroom-ID>] [<reason>]
1719 * Chatroom-ID is not a must to specify a reason.
1720 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1721 * MUC then the current chatroom should be parted and srtv[1] should
1722 * be treated as part of the optional part-message. */
1723 message = g_strconcat (strv[1], " ", strv[2], NULL);
1724 tp_chat = empathy_chat_get_tp_chat (chat);
1726 empathy_tp_chat_leave (tp_chat, message);
1732 static GtkNotebook *
1733 notebook_create_window_cb (GtkNotebook *source,
1739 EmpathyChatWindowPriv *priv;
1740 EmpathyChatWindow *window, *new_window;
1743 chat = EMPATHY_CHAT (page);
1744 window = chat_window_find_chat (chat);
1746 new_window = empathy_chat_window_new ();
1747 priv = GET_PRIV (new_window);
1749 DEBUG ("Detach hook called");
1751 empathy_chat_window_move_chat (window, new_window, chat);
1753 gtk_widget_show (priv->dialog);
1754 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1760 chat_window_page_switched_cb (GtkNotebook *notebook,
1763 EmpathyChatWindow *window)
1765 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1766 EmpathyChat *chat = EMPATHY_CHAT (child);
1768 DEBUG ("Page switched");
1770 if (priv->page_added) {
1771 priv->page_added = FALSE;
1772 empathy_chat_scroll_down (chat);
1774 else if (priv->current_chat == chat) {
1778 priv->current_chat = chat;
1779 empathy_chat_messages_read (chat);
1781 chat_window_update_chat_tab (chat);
1785 chat_window_page_added_cb (GtkNotebook *notebook,
1788 EmpathyChatWindow *window)
1790 EmpathyChatWindowPriv *priv;
1793 priv = GET_PRIV (window);
1795 /* If we just received DND to the same window, we don't want
1796 * to do anything here like removing the tab and then readding
1797 * it, so we return here and in "page-added".
1799 if (priv->dnd_same_window) {
1800 DEBUG ("Page added (back to the same window)");
1801 priv->dnd_same_window = FALSE;
1805 DEBUG ("Page added");
1807 /* Get chat object */
1808 chat = EMPATHY_CHAT (child);
1810 /* Connect chat signals for this window */
1811 g_signal_connect (chat, "composing",
1812 G_CALLBACK (chat_window_composing_cb),
1814 g_signal_connect (chat, "new-message",
1815 G_CALLBACK (chat_window_new_message_cb),
1817 g_signal_connect (chat, "part-command-entered",
1818 G_CALLBACK (chat_window_command_part),
1820 g_signal_connect (chat, "notify::tp-chat",
1821 G_CALLBACK (chat_window_update_chat_tab),
1824 /* Set flag so we know to perform some special operations on
1825 * switch page due to the new page being added.
1827 priv->page_added = TRUE;
1829 /* Get list of chats up to date */
1830 priv->chats = g_list_append (priv->chats, chat);
1832 chat_window_update_chat_tab (chat);
1836 chat_window_page_removed_cb (GtkNotebook *notebook,
1839 EmpathyChatWindow *window)
1841 EmpathyChatWindowPriv *priv;
1844 priv = GET_PRIV (window);
1846 /* If we just received DND to the same window, we don't want
1847 * to do anything here like removing the tab and then readding
1848 * it, so we return here and in "page-added".
1850 if (priv->dnd_same_window) {
1851 DEBUG ("Page removed (and will be readded to same window)");
1855 DEBUG ("Page removed");
1857 /* Get chat object */
1858 chat = EMPATHY_CHAT (child);
1860 /* Disconnect all signal handlers for this chat and this window */
1861 g_signal_handlers_disconnect_by_func (chat,
1862 G_CALLBACK (chat_window_composing_cb),
1864 g_signal_handlers_disconnect_by_func (chat,
1865 G_CALLBACK (chat_window_new_message_cb),
1867 g_signal_handlers_disconnect_by_func (chat,
1868 G_CALLBACK (chat_window_update_chat_tab),
1871 /* Keep list of chats up to date */
1872 priv->chats = g_list_remove (priv->chats, chat);
1873 empathy_chat_messages_read (chat);
1875 if (priv->chats == NULL) {
1876 g_object_unref (window);
1878 chat_window_update (window, TRUE);
1883 chat_window_focus_in_event_cb (GtkWidget *widget,
1885 EmpathyChatWindow *window)
1887 EmpathyChatWindowPriv *priv;
1889 priv = GET_PRIV (window);
1891 empathy_chat_messages_read (priv->current_chat);
1893 chat_window_set_urgency_hint (window, FALSE);
1895 /* Update the title, since we now mark all unread messages as read. */
1896 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1902 chat_window_drag_drop (GtkWidget *widget,
1903 GdkDragContext *context,
1907 EmpathyChatWindow *window)
1910 EmpathyChatWindowPriv *priv;
1912 priv = GET_PRIV (window);
1914 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1915 if (target == GDK_NONE)
1916 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1918 if (target != GDK_NONE) {
1919 gtk_drag_get_data (widget, context, target, time_);
1927 chat_window_drag_motion (GtkWidget *widget,
1928 GdkDragContext *context,
1932 EmpathyChatWindow *window)
1935 EmpathyChatWindowPriv *priv;
1937 priv = GET_PRIV (window);
1939 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1940 if (target != GDK_NONE) {
1941 /* This is a file drag. Ensure the contact is online and set the
1942 drag type to COPY. Note that it's possible that the tab will
1943 be switched by GTK+ after a timeout from drag_motion without
1944 getting another drag_motion to disable the drop. You have
1945 to hold your mouse really still.
1947 EmpathyContact *contact;
1949 priv = GET_PRIV (window);
1950 contact = empathy_chat_get_remote_contact (priv->current_chat);
1951 /* contact is NULL for multi-user chats. We don't do
1952 * file transfers to MUCs. We also don't send files
1953 * to offline contacts or contacts that don't support
1956 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1957 gdk_drag_status (context, 0, time_);
1960 if (!(empathy_contact_get_capabilities (contact)
1961 & EMPATHY_CAPABILITIES_FT)) {
1962 gdk_drag_status (context, 0, time_);
1965 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1969 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1970 if (target != GDK_NONE) {
1971 /* This is a drag of a contact from a contact list. Set to COPY.
1972 FIXME: If this drag is to a MUC window, it invites the user.
1973 Otherwise, it opens a chat. Should we use a different drag
1974 type for invites? Should we allow ASK?
1976 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1984 chat_window_drag_data_received (GtkWidget *widget,
1985 GdkDragContext *context,
1988 GtkSelectionData *selection,
1991 EmpathyChatWindow *window)
1993 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1994 EmpathyChat *chat = NULL;
1995 EmpathyChatWindow *old_window;
1996 TpAccount *account = NULL;
1997 EmpathyClientFactory *factory;
2000 const gchar *account_id;
2001 const gchar *contact_id;
2003 id = (const gchar*) gtk_selection_data_get_data (selection);
2005 factory = empathy_client_factory_dup ();
2007 DEBUG ("DND contact from roster with id:'%s'", id);
2009 strv = g_strsplit (id, ":", 2);
2010 if (g_strv_length (strv) == 2) {
2011 account_id = strv[0];
2012 contact_id = strv[1];
2014 tp_simple_client_factory_ensure_account (
2015 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2018 g_object_unref (factory);
2019 if (account != NULL)
2020 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2023 if (account == NULL) {
2025 gtk_drag_finish (context, FALSE, FALSE, time_);
2030 empathy_chat_with_contact_id (
2031 account, contact_id,
2032 empathy_get_current_action_time (),
2040 old_window = chat_window_find_chat (chat);
2042 if (old_window == window) {
2043 gtk_drag_finish (context, TRUE, FALSE, time_);
2047 empathy_chat_window_move_chat (old_window, window, chat);
2049 empathy_chat_window_add_chat (window, chat);
2052 /* Added to take care of any outstanding chat events */
2053 empathy_chat_window_present_chat (chat,
2054 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2056 /* We should return TRUE to remove the data when doing
2057 * GDK_ACTION_MOVE, but we don't here otherwise it has
2058 * weird consequences, and we handle that internally
2059 * anyway with add_chat () and remove_chat ().
2061 gtk_drag_finish (context, TRUE, FALSE, time_);
2063 else if (info == DND_DRAG_TYPE_URI_LIST) {
2064 EmpathyChatWindowPriv *priv;
2065 EmpathyContact *contact;
2068 priv = GET_PRIV (window);
2069 contact = empathy_chat_get_remote_contact (priv->current_chat);
2071 /* contact is NULL when current_chat is a multi-user chat.
2072 * We don't do file transfers to MUCs, so just cancel the drag.
2074 if (contact == NULL) {
2075 gtk_drag_finish (context, TRUE, FALSE, time_);
2079 data = (const gchar *) gtk_selection_data_get_data (selection);
2080 empathy_send_file_from_uri_list (contact, data);
2082 gtk_drag_finish (context, TRUE, FALSE, time_);
2084 else if (info == DND_DRAG_TYPE_TAB) {
2086 EmpathyChatWindow *old_window = NULL;
2090 chat = (void *) gtk_selection_data_get_data (selection);
2091 old_window = chat_window_find_chat (*chat);
2094 EmpathyChatWindowPriv *priv;
2096 priv = GET_PRIV (window);
2097 priv->dnd_same_window = (old_window == window);
2098 DEBUG ("DND tab (within same window: %s)",
2099 priv->dnd_same_window ? "Yes" : "No");
2102 DEBUG ("DND from unknown source");
2103 gtk_drag_finish (context, FALSE, FALSE, time_);
2108 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2109 guint num_chats_in_manager,
2110 EmpathyChatWindow *window)
2112 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2114 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2115 num_chats_in_manager > 0);
2119 chat_window_finalize (GObject *object)
2121 EmpathyChatWindow *window;
2122 EmpathyChatWindowPriv *priv;
2124 window = EMPATHY_CHAT_WINDOW (object);
2125 priv = GET_PRIV (window);
2127 DEBUG ("Finalized: %p", object);
2129 g_object_unref (priv->ui_manager);
2130 g_object_unref (priv->chatroom_manager);
2131 g_object_unref (priv->notify_mgr);
2132 g_object_unref (priv->gsettings_chat);
2133 g_object_unref (priv->gsettings_notif);
2134 g_object_unref (priv->gsettings_ui);
2135 g_object_unref (priv->sound_mgr);
2137 if (priv->notification != NULL) {
2138 notify_notification_close (priv->notification, NULL);
2139 priv->notification = NULL;
2142 if (priv->contact_targets) {
2143 gtk_target_list_unref (priv->contact_targets);
2145 if (priv->file_targets) {
2146 gtk_target_list_unref (priv->file_targets);
2149 if (priv->chat_manager) {
2150 g_signal_handler_disconnect (priv->chat_manager,
2151 priv->chat_manager_chats_changed_id);
2152 g_object_unref (priv->chat_manager);
2153 priv->chat_manager = NULL;
2156 chat_windows = g_list_remove (chat_windows, window);
2157 gtk_widget_destroy (priv->dialog);
2159 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2163 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2165 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2167 object_class->finalize = chat_window_finalize;
2169 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2173 empathy_chat_window_init (EmpathyChatWindow *window)
2176 GtkAccelGroup *accel_group;
2181 GtkWidget *chat_vbox;
2183 EmpathySmileyManager *smiley_manager;
2184 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2185 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2187 window->priv = priv;
2188 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2189 gui = empathy_builder_get_file (filename,
2190 "chat_window", &priv->dialog,
2191 "chat_vbox", &chat_vbox,
2192 "ui_manager", &priv->ui_manager,
2193 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2194 "menu_conv_favorite", &priv->menu_conv_favorite,
2195 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2196 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2197 "menu_edit_cut", &priv->menu_edit_cut,
2198 "menu_edit_copy", &priv->menu_edit_copy,
2199 "menu_edit_paste", &priv->menu_edit_paste,
2200 "menu_edit_find", &priv->menu_edit_find,
2201 "menu_tabs_next", &priv->menu_tabs_next,
2202 "menu_tabs_prev", &priv->menu_tabs_prev,
2203 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2204 "menu_tabs_left", &priv->menu_tabs_left,
2205 "menu_tabs_right", &priv->menu_tabs_right,
2206 "menu_tabs_detach", &priv->menu_tabs_detach,
2210 empathy_builder_connect (gui, window,
2211 "menu_conv", "activate", chat_window_conv_activate_cb,
2212 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2213 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2214 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2215 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2216 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2217 "menu_conv_close", "activate", chat_window_close_activate_cb,
2218 "menu_edit", "activate", chat_window_edit_activate_cb,
2219 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2220 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2221 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2222 "menu_edit_find", "activate", chat_window_find_activate_cb,
2223 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2224 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2225 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2226 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2227 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2228 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2229 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2230 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2233 g_object_ref (priv->ui_manager);
2234 g_object_unref (gui);
2236 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2237 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2238 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2239 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2241 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2243 priv->notebook = gtk_notebook_new ();
2245 g_signal_connect (priv->notebook, "create-window",
2246 G_CALLBACK (notebook_create_window_cb), window);
2248 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2249 "EmpathyChatWindow");
2250 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2251 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2252 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2253 gtk_widget_show (priv->notebook);
2256 accel_group = gtk_accel_group_new ();
2257 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2259 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2260 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2263 gtk_accel_group_connect (accel_group,
2270 g_object_unref (accel_group);
2272 /* Set up drag target lists */
2273 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2274 G_N_ELEMENTS (drag_types_dest_contact));
2275 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2276 G_N_ELEMENTS (drag_types_dest_file));
2278 /* Set up smiley menu */
2279 smiley_manager = empathy_smiley_manager_dup_singleton ();
2280 submenu = empathy_smiley_menu_new (smiley_manager,
2281 chat_window_insert_smiley_activate_cb,
2283 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2284 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2285 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2286 g_object_unref (smiley_manager);
2288 /* Set up signals we can't do with ui file since we may need to
2289 * block/unblock them at some later stage.
2292 g_signal_connect (priv->dialog,
2294 G_CALLBACK (chat_window_delete_event_cb),
2296 g_signal_connect (priv->dialog,
2298 G_CALLBACK (chat_window_focus_in_event_cb),
2300 g_signal_connect_after (priv->notebook,
2302 G_CALLBACK (chat_window_page_switched_cb),
2304 g_signal_connect (priv->notebook,
2306 G_CALLBACK (chat_window_page_added_cb),
2308 g_signal_connect (priv->notebook,
2310 G_CALLBACK (chat_window_page_removed_cb),
2313 /* Set up drag and drop */
2314 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2315 GTK_DEST_DEFAULT_HIGHLIGHT,
2317 G_N_ELEMENTS (drag_types_dest),
2318 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2320 /* connect_after to allow GtkNotebook's built-in tab switching */
2321 g_signal_connect_after (priv->notebook,
2323 G_CALLBACK (chat_window_drag_motion),
2325 g_signal_connect (priv->notebook,
2326 "drag-data-received",
2327 G_CALLBACK (chat_window_drag_data_received),
2329 g_signal_connect (priv->notebook,
2331 G_CALLBACK (chat_window_drag_drop),
2334 chat_windows = g_list_prepend (chat_windows, window);
2336 /* Set up private details */
2338 priv->current_chat = NULL;
2339 priv->notification = NULL;
2341 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2343 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2344 priv->chat_manager_chats_changed_id =
2345 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2346 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2349 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2350 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2354 /* Returns the window to open a new tab in if there is a suitable window,
2355 * otherwise, returns NULL indicating that a new window should be added.
2357 static EmpathyChatWindow *
2358 empathy_chat_window_get_default (gboolean room)
2360 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2362 gboolean separate_windows = TRUE;
2364 separate_windows = g_settings_get_boolean (gsettings,
2365 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2367 g_object_unref (gsettings);
2369 if (separate_windows) {
2370 /* Always create a new window */
2374 for (l = chat_windows; l; l = l->next) {
2375 EmpathyChatWindow *chat_window;
2376 guint nb_rooms, nb_private;
2378 chat_window = l->data;
2380 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2382 /* Skip the window if there aren't any rooms in it */
2383 if (room && nb_rooms == 0)
2386 /* Skip the window if there aren't any 1-1 chats in it */
2387 if (!room && nb_private == 0)
2397 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2400 EmpathyChatWindowPriv *priv;
2402 GtkWidget *popup_label;
2404 GValue value = { 0, };
2406 g_return_if_fail (window != NULL);
2407 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2409 priv = GET_PRIV (window);
2411 /* Reference the chat object */
2412 g_object_ref (chat);
2414 /* If this window has just been created, position it */
2415 if (priv->chats == NULL) {
2416 const gchar *name = "chat-window";
2417 gboolean separate_windows;
2419 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2420 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2422 if (empathy_chat_is_room (chat))
2423 name = "room-window";
2425 if (separate_windows) {
2428 /* Save current position of the window */
2429 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2431 /* First bind to the 'generic' name. So new window for which we didn't
2432 * save a geometry yet will have the geometry of the last saved
2433 * window (bgo #601191). */
2434 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2436 /* Restore previous position of the window so the newly created window
2437 * won't be in the same position as the latest saved window and so
2438 * completely hide it. */
2439 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2441 /* Then bind it to the name of the contact/room so we'll save the
2442 * geometry specific to this window */
2443 name = empathy_chat_get_id (chat);
2446 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2449 child = GTK_WIDGET (chat);
2450 label = chat_window_create_label (window, chat, TRUE);
2451 popup_label = chat_window_create_label (window, chat, FALSE);
2452 gtk_widget_show (child);
2454 g_signal_connect (chat, "notify::name",
2455 G_CALLBACK (chat_window_chat_notify_cb),
2457 g_signal_connect (chat, "notify::subject",
2458 G_CALLBACK (chat_window_chat_notify_cb),
2460 g_signal_connect (chat, "notify::remote-contact",
2461 G_CALLBACK (chat_window_chat_notify_cb),
2463 g_signal_connect (chat, "notify::sms-channel",
2464 G_CALLBACK (chat_window_chat_notify_cb),
2466 g_signal_connect (chat, "notify::n-messages-sending",
2467 G_CALLBACK (chat_window_chat_notify_cb),
2469 g_signal_connect (chat, "notify::nb-unread-messages",
2470 G_CALLBACK (chat_window_chat_notify_cb),
2472 chat_window_chat_notify_cb (chat);
2474 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2475 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2476 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2477 g_value_init (&value, G_TYPE_BOOLEAN);
2478 g_value_set_boolean (&value, TRUE);
2479 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2480 child, "tab-expand" , &value);
2481 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2482 child, "tab-fill" , &value);
2483 g_value_unset (&value);
2485 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2489 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2492 EmpathyChatWindowPriv *priv;
2494 EmpathyContact *remote_contact;
2495 EmpathyChatManager *chat_manager;
2497 g_return_if_fail (window != NULL);
2498 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2500 priv = GET_PRIV (window);
2502 g_signal_handlers_disconnect_by_func (chat,
2503 chat_window_chat_notify_cb,
2505 remote_contact = g_object_get_data (G_OBJECT (chat),
2506 "chat-window-remote-contact");
2507 if (remote_contact) {
2508 g_signal_handlers_disconnect_by_func (remote_contact,
2509 chat_window_update_chat_tab,
2513 chat_manager = empathy_chat_manager_dup_singleton ();
2514 empathy_chat_manager_closed_chat (chat_manager, chat);
2515 g_object_unref (chat_manager);
2517 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2519 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2521 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2523 g_object_unref (chat);
2527 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2528 EmpathyChatWindow *new_window,
2533 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2534 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2535 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2537 widget = GTK_WIDGET (chat);
2539 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2540 G_OBJECT (widget)->ref_count);
2542 /* We reference here to make sure we don't loose the widget
2543 * and the EmpathyChat object during the move.
2545 g_object_ref (chat);
2546 g_object_ref (widget);
2548 empathy_chat_window_remove_chat (old_window, chat);
2549 empathy_chat_window_add_chat (new_window, chat);
2551 g_object_unref (widget);
2552 g_object_unref (chat);
2556 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2559 EmpathyChatWindowPriv *priv;
2562 g_return_if_fail (window != NULL);
2563 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2565 priv = GET_PRIV (window);
2567 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2569 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2574 empathy_chat_window_find_chat (TpAccount *account,
2576 gboolean sms_channel)
2580 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2582 for (l = chat_windows; l; l = l->next) {
2583 EmpathyChatWindowPriv *priv;
2584 EmpathyChatWindow *window;
2588 priv = GET_PRIV (window);
2590 for (ll = priv->chats; ll; ll = ll->next) {
2595 if (account == empathy_chat_get_account (chat) &&
2596 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2597 sms_channel == empathy_chat_is_sms_channel (chat)) {
2607 empathy_chat_window_present_chat (EmpathyChat *chat,
2610 EmpathyChatWindow *window;
2611 EmpathyChatWindowPriv *priv;
2612 guint32 x_timestamp;
2614 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2616 window = chat_window_find_chat (chat);
2618 /* If the chat has no window, create one */
2619 if (window == NULL) {
2620 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2622 window = empathy_chat_window_new ();
2624 /* we want to display the newly created window even if we don't present
2626 priv = GET_PRIV (window);
2627 gtk_widget_show (priv->dialog);
2630 empathy_chat_window_add_chat (window, chat);
2633 /* Don't force the window to show itself when it wasn't
2634 * an action by the user
2636 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2639 priv = GET_PRIV (window);
2641 if (x_timestamp != GDK_CURRENT_TIME) {
2642 /* Don't present or switch tab if the action was earlier than the
2643 * last actions X time, accounting for overflow and the first ever
2646 if (priv->x_user_action_time != 0
2647 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2650 priv->x_user_action_time = x_timestamp;
2653 empathy_chat_window_switch_to_chat (window, chat);
2655 /* Don't use empathy_window_present_with_time () which would move the window
2656 * to our current desktop but move to the window's desktop instead. This is
2657 * more coherent with Shell's 'app is ready' notication which moves the view
2658 * to the app desktop rather than moving the app itself. */
2659 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2661 gtk_widget_grab_focus (chat->input_text_view);
2665 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2669 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2671 guint _nb_rooms = 0, _nb_private = 0;
2673 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2674 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2680 if (nb_rooms != NULL)
2681 *nb_rooms = _nb_rooms;
2682 if (nb_private != NULL)
2683 *nb_private = _nb_private;