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-request-util.h>
49 #include <libempathy/empathy-individual-manager.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_INDIVIDUAL_ID,
133 DND_DRAG_TYPE_URI_LIST,
137 static const GtkTargetEntry drag_types_dest[] = {
138 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
139 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
140 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
141 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
142 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
145 static const GtkTargetEntry drag_types_dest_contact[] = {
146 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
147 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
150 static const GtkTargetEntry drag_types_dest_file[] = {
151 /* must be first to be prioritized, in order to receive the
152 * note's file path from Tomboy instead of an URI */
153 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
154 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
157 static void chat_window_update (EmpathyChatWindow *window,
158 gboolean update_contact_menu);
160 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
163 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
166 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
167 EmpathyChatWindow *new_window,
170 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
174 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
177 chat_window_accel_cb (GtkAccelGroup *accelgroup,
181 EmpathyChatWindow *window)
183 EmpathyChatWindowPriv *priv;
187 priv = GET_PRIV (window);
189 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
190 if (tab_accel_keys[i] == key) {
197 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
201 static EmpathyChatWindow *
202 chat_window_find_chat (EmpathyChat *chat)
204 EmpathyChatWindowPriv *priv;
207 for (l = chat_windows; l; l = l->next) {
208 priv = GET_PRIV (l->data);
209 ll = g_list_find (priv->chats, chat);
219 remove_all_chats (EmpathyChatWindow *window)
221 EmpathyChatWindowPriv *priv;
223 priv = GET_PRIV (window);
224 g_object_ref (window);
226 while (priv->chats) {
227 empathy_chat_window_remove_chat (window, priv->chats->data);
230 g_object_unref (window);
234 confirm_close_response_cb (GtkWidget *dialog,
236 EmpathyChatWindow *window)
240 chat = g_object_get_data (G_OBJECT (dialog), "chat");
242 gtk_widget_destroy (dialog);
244 if (response != GTK_RESPONSE_ACCEPT)
248 empathy_chat_window_remove_chat (window, chat);
250 remove_all_chats (window);
255 confirm_close (EmpathyChatWindow *window,
256 gboolean close_window,
260 EmpathyChatWindowPriv *priv;
262 gchar *primary, *secondary;
264 g_return_if_fail (n_rooms > 0);
267 g_return_if_fail (chat == NULL);
269 g_return_if_fail (chat != NULL);
272 priv = GET_PRIV (window);
274 /* If there are no chats in this window, how could we possibly have got
277 g_return_if_fail (priv->chats != NULL);
279 /* Treat closing a window which only has one tab exactly like closing
282 if (close_window && priv->chats->next == NULL) {
283 close_window = FALSE;
284 chat = priv->chats->data;
288 primary = g_strdup (_("Close this window?"));
291 gchar *chat_name = empathy_chat_dup_name (chat);
292 secondary = g_strdup_printf (
293 _("Closing this window will leave %s. You will "
294 "not receive any further messages until you "
299 secondary = g_strdup_printf (
300 /* Note to translators: the number of chats will
301 * always be at least 2.
304 "Closing this window will leave a chat room. You will "
305 "not receive any further messages until you rejoin it.",
306 "Closing this window will leave %u chat rooms. You will "
307 "not receive any further messages until you rejoin them.",
312 gchar *chat_name = empathy_chat_dup_name (chat);
313 primary = g_strdup_printf (_("Leave %s?"), chat_name);
314 secondary = g_strdup (_("You will not receive any further messages from this chat "
315 "room until you rejoin it."));
319 dialog = gtk_message_dialog_new (
320 GTK_WINDOW (priv->dialog),
321 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
326 gtk_window_set_title (GTK_WINDOW (dialog), "");
327 g_object_set (dialog, "secondary-text", secondary, NULL);
332 gtk_dialog_add_button (GTK_DIALOG (dialog),
333 close_window ? _("Close window") : _("Leave room"),
334 GTK_RESPONSE_ACCEPT);
335 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
336 GTK_RESPONSE_ACCEPT);
339 g_object_set_data (G_OBJECT (dialog), "chat", chat);
342 g_signal_connect (dialog, "response",
343 G_CALLBACK (confirm_close_response_cb), window);
345 gtk_window_present (GTK_WINDOW (dialog));
348 /* Returns TRUE if we should check if the user really wants to leave. If it's
349 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
350 * the user is actually in the room as opposed to having been kicked or gone
351 * offline or something), then we should check.
354 chat_needs_close_confirmation (EmpathyChat *chat)
356 return (empathy_chat_is_room (chat)
357 && empathy_chat_get_tp_chat (chat) != NULL);
361 maybe_close_chat (EmpathyChatWindow *window,
364 g_return_if_fail (chat != NULL);
366 if (chat_needs_close_confirmation (chat)) {
367 confirm_close (window, FALSE, 1, chat);
369 empathy_chat_window_remove_chat (window, chat);
374 chat_window_close_clicked_cb (GtkAction *action,
377 EmpathyChatWindow *window;
379 window = chat_window_find_chat (chat);
380 maybe_close_chat (window, chat);
384 chat_tab_style_updated_cb (GtkWidget *hbox,
388 int char_width, h, w;
389 PangoContext *context;
390 const PangoFontDescription *font_desc;
391 PangoFontMetrics *metrics;
393 button = g_object_get_data (G_OBJECT (user_data),
394 "chat-window-tab-close-button");
395 context = gtk_widget_get_pango_context (hbox);
397 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
398 GTK_STATE_FLAG_NORMAL);
400 metrics = pango_context_get_metrics (context, font_desc,
401 pango_context_get_language (context));
402 char_width = pango_font_metrics_get_approximate_char_width (metrics);
403 pango_font_metrics_unref (metrics);
405 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
406 GTK_ICON_SIZE_MENU, &w, &h);
408 /* Request at least about 12 chars width plus at least space for the status
409 * image and the close button */
410 gtk_widget_set_size_request (hbox,
411 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
413 gtk_widget_set_size_request (button, w, h);
417 chat_window_create_label (EmpathyChatWindow *window,
419 gboolean is_tab_label)
422 GtkWidget *name_label;
423 GtkWidget *status_image;
424 GtkWidget *event_box;
425 GtkWidget *event_box_hbox;
426 PangoAttrList *attr_list;
427 PangoAttribute *attr;
429 /* The spacing between the button and the label. */
430 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
432 event_box = gtk_event_box_new ();
433 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
435 name_label = gtk_label_new (NULL);
437 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
439 attr_list = pango_attr_list_new ();
440 attr = pango_attr_scale_new (1/1.2);
441 attr->start_index = 0;
442 attr->end_index = -1;
443 pango_attr_list_insert (attr_list, attr);
444 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
445 pango_attr_list_unref (attr_list);
447 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
448 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
449 g_object_set_data (G_OBJECT (chat),
450 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
453 status_image = gtk_image_new ();
455 /* Spacing between the icon and label. */
456 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
458 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
459 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
461 g_object_set_data (G_OBJECT (chat),
462 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
464 g_object_set_data (G_OBJECT (chat),
465 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
468 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
469 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
472 GtkWidget *close_button;
473 GtkWidget *sending_spinner;
475 sending_spinner = gtk_spinner_new ();
477 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
479 g_object_set_data (G_OBJECT (chat),
480 "chat-window-tab-sending-spinner",
483 close_button = gedit_close_button_new ();
484 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
486 /* We don't want focus/keynav for the button to avoid clutter, and
487 * Ctrl-W works anyway.
489 gtk_widget_set_can_focus (close_button, FALSE);
490 gtk_widget_set_can_default (close_button, FALSE);
492 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
494 g_signal_connect (close_button,
496 G_CALLBACK (chat_window_close_clicked_cb),
499 /* React to theme changes and also setup the size correctly. */
500 g_signal_connect (hbox,
502 G_CALLBACK (chat_tab_style_updated_cb),
506 gtk_widget_show_all (hbox);
512 _submenu_notify_visible_changed_cb (GObject *object,
516 g_signal_handlers_disconnect_by_func (object,
517 _submenu_notify_visible_changed_cb,
519 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
523 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
528 gboolean wrap_around;
529 gboolean is_connected;
532 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
533 first_page = (page_num == 0);
534 last_page = (page_num == (num_pages - 1));
535 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
537 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
539 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
541 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
543 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
544 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
545 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
546 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
550 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
551 EmpathyChatWindow *self)
553 EmpathyTpChat *tp_chat;
554 TpConnection *connection;
556 gboolean sensitive = FALSE;
558 g_return_if_fail (priv->current_chat != NULL);
560 action = gtk_ui_manager_get_action (priv->ui_manager,
561 "/chats_menubar/menu_conv/menu_conv_invite_participant");
562 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
564 if (tp_chat != NULL) {
565 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
567 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
568 (tp_connection_get_status (connection, NULL) ==
569 TP_CONNECTION_STATUS_CONNECTED);
572 gtk_action_set_sensitive (action, sensitive);
576 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
577 EmpathyChatWindow *window)
579 GtkWidget *menu, *submenu, *orig_submenu;
581 menu = gtk_ui_manager_get_widget (priv->ui_manager,
582 "/chats_menubar/menu_contact");
583 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
585 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
586 submenu = empathy_chat_get_contact_menu (priv->current_chat);
588 if (submenu != NULL) {
589 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
590 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
592 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
593 gtk_widget_show (menu);
594 gtk_widget_set_sensitive (menu, TRUE);
596 gtk_widget_set_sensitive (menu, FALSE);
599 tp_g_signal_connect_object (orig_submenu,
601 (GCallback)_submenu_notify_visible_changed_cb,
607 get_all_unread_messages (EmpathyChatWindowPriv *priv)
612 for (l = priv->chats; l != NULL; l = g_list_next (l))
613 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
619 get_window_title_name (EmpathyChatWindowPriv *priv)
621 gchar *active_name, *ret;
623 guint current_unread_msgs;
625 nb_chats = g_list_length (priv->chats);
626 g_assert (nb_chats > 0);
628 active_name = empathy_chat_dup_name (priv->current_chat);
630 current_unread_msgs = empathy_chat_get_nb_unread_messages (
635 if (current_unread_msgs == 0)
636 ret = g_strdup (active_name);
638 ret = g_strdup_printf (ngettext (
640 "%s (%d unread)", current_unread_msgs),
641 active_name, current_unread_msgs);
643 guint nb_others = nb_chats - 1;
644 guint all_unread_msgs;
646 all_unread_msgs = get_all_unread_messages (priv);
648 if (all_unread_msgs == 0) {
649 /* no unread message */
650 ret = g_strdup_printf (ngettext (
652 "%s (and %u others)", nb_others),
653 active_name, nb_others);
656 else if (all_unread_msgs == current_unread_msgs) {
657 /* unread messages are in the current tab */
658 ret = g_strdup_printf (ngettext (
660 "%s (%d unread)", current_unread_msgs),
661 active_name, current_unread_msgs);
664 else if (current_unread_msgs == 0) {
665 /* unread messages are in other tabs */
666 ret = g_strdup_printf (ngettext (
667 "%s (%d unread from others)",
668 "%s (%d unread from others)",
670 active_name, all_unread_msgs);
674 /* unread messages are in all the tabs */
675 ret = g_strdup_printf (ngettext (
676 "%s (%d unread from all)",
677 "%s (%d unread from all)",
679 active_name, all_unread_msgs);
683 g_free (active_name);
689 chat_window_title_update (EmpathyChatWindowPriv *priv)
693 name = get_window_title_name (priv);
694 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
699 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
702 EmpathyContact *remote_contact;
703 gboolean avatar_in_icon;
706 n_chats = g_list_length (priv->chats);
708 /* Update window icon */
710 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
711 EMPATHY_IMAGE_MESSAGE);
713 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
714 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
716 if (n_chats == 1 && avatar_in_icon) {
717 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
718 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
719 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
722 g_object_unref (icon);
725 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
731 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
735 GtkWidget *chat_close_button;
738 if (num_pages == 1) {
739 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
740 chat_close_button = g_object_get_data (G_OBJECT (chat),
741 "chat-window-tab-close-button");
742 gtk_widget_hide (chat_close_button);
744 for (i=0; i<num_pages; i++) {
745 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
746 chat_close_button = g_object_get_data (G_OBJECT (chat),
747 "chat-window-tab-close-button");
748 gtk_widget_show (chat_close_button);
754 chat_window_update (EmpathyChatWindow *window,
755 gboolean update_contact_menu)
757 EmpathyChatWindowPriv *priv = GET_PRIV (window);
760 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
762 /* Update Tab menu */
763 chat_window_menu_context_update (priv,
766 chat_window_conversation_menu_update (priv, window);
768 /* If this update is due to a focus-in event, we know the menu will be
769 the same as when we last left it, so no work to do. Besides, if we
770 swap out the menu on a focus-in, we may confuse any external global
772 if (update_contact_menu) {
773 chat_window_contact_menu_update (priv,
777 chat_window_title_update (priv);
779 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
781 chat_window_close_button_update (priv,
786 append_markup_printf (GString *string,
793 va_start (args, format);
795 tmp = g_markup_vprintf_escaped (format, args);
796 g_string_append (string, tmp);
803 chat_window_update_chat_tab_full (EmpathyChat *chat,
804 gboolean update_contact_menu)
806 EmpathyChatWindow *window;
807 EmpathyChatWindowPriv *priv;
808 EmpathyContact *remote_contact;
812 const gchar *subject;
813 const gchar *status = NULL;
817 const gchar *icon_name;
818 GtkWidget *tab_image;
819 GtkWidget *menu_image;
820 GtkWidget *sending_spinner;
823 window = chat_window_find_chat (chat);
827 priv = GET_PRIV (window);
829 /* Get information */
830 name = empathy_chat_dup_name (chat);
831 account = empathy_chat_get_account (chat);
832 subject = empathy_chat_get_subject (chat);
833 remote_contact = empathy_chat_get_remote_contact (chat);
835 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
836 name, tp_proxy_get_object_path (account), subject, remote_contact);
838 /* Update tab image */
839 if (empathy_chat_get_tp_chat (chat) == NULL) {
840 /* No TpChat, we are disconnected */
843 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
844 icon_name = EMPATHY_IMAGE_MESSAGE;
846 else if (remote_contact && empathy_chat_is_composing (chat)) {
847 icon_name = EMPATHY_IMAGE_TYPING;
849 else if (empathy_chat_is_sms_channel (chat)) {
850 icon_name = EMPATHY_IMAGE_SMS;
852 else if (remote_contact) {
853 icon_name = empathy_icon_name_for_contact (remote_contact);
855 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
858 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
859 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
860 if (icon_name != NULL) {
861 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
862 gtk_widget_show (tab_image);
863 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
864 gtk_widget_show (menu_image);
866 gtk_widget_hide (tab_image);
867 gtk_widget_hide (menu_image);
870 /* Update the sending spinner */
871 nb_sending = empathy_chat_get_n_messages_sending (chat);
872 sending_spinner = g_object_get_data (G_OBJECT (chat),
873 "chat-window-tab-sending-spinner");
875 g_object_set (sending_spinner,
876 "active", nb_sending > 0,
877 "visible", nb_sending > 0,
880 /* Update tab tooltip */
881 tooltip = g_string_new (NULL);
883 if (remote_contact) {
884 id = empathy_contact_get_id (remote_contact);
885 status = empathy_contact_get_presence_message (remote_contact);
890 if (empathy_chat_is_sms_channel (chat)) {
891 append_markup_printf (tooltip, "%s ", _("SMS:"));
894 append_markup_printf (tooltip,
895 "<b>%s</b><small> (%s)</small>",
897 tp_account_get_display_name (account));
899 if (nb_sending > 0) {
900 char *tmp = g_strdup_printf (
901 ngettext ("Sending %d message",
902 "Sending %d messages",
906 g_string_append (tooltip, "\n");
907 g_string_append (tooltip, tmp);
909 gtk_widget_set_tooltip_text (sending_spinner, tmp);
913 if (!EMP_STR_EMPTY (status)) {
914 append_markup_printf (tooltip, "\n<i>%s</i>", status);
918 append_markup_printf (tooltip, "\n<b>%s</b> %s",
919 _("Topic:"), subject);
922 if (remote_contact && empathy_chat_is_composing (chat)) {
923 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
926 if (remote_contact != NULL) {
927 const gchar * const *types;
929 types = empathy_contact_get_client_types (remote_contact);
930 if (types != NULL && !tp_strdiff (types[0], "phone")) {
931 /* I'm on a phone ! */
934 name = g_strdup_printf ("☎ %s", name);
939 markup = g_string_free (tooltip, FALSE);
940 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
941 gtk_widget_set_tooltip_markup (widget, markup);
942 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
943 gtk_widget_set_tooltip_markup (widget, markup);
946 /* Update tab and menu label */
947 if (empathy_chat_is_highlighted (chat)) {
948 markup = g_markup_printf_escaped (
949 "<span color=\"red\" weight=\"bold\">%s</span>",
952 markup = g_markup_escape_text (name, -1);
955 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
956 gtk_label_set_markup (GTK_LABEL (widget), markup);
957 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
958 gtk_label_set_markup (GTK_LABEL (widget), markup);
961 /* Update the window if it's the current chat */
962 if (priv->current_chat == chat) {
963 chat_window_update (window, update_contact_menu);
970 chat_window_update_chat_tab (EmpathyChat *chat)
972 chat_window_update_chat_tab_full (chat, TRUE);
976 chat_window_chat_notify_cb (EmpathyChat *chat)
978 EmpathyChatWindow *window;
979 EmpathyContact *old_remote_contact;
980 EmpathyContact *remote_contact = NULL;
982 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
983 remote_contact = empathy_chat_get_remote_contact (chat);
985 if (old_remote_contact != remote_contact) {
986 /* The remote-contact associated with the chat changed, we need
987 * to keep track of any change of that contact and update the
988 * window each time. */
989 if (remote_contact) {
990 g_signal_connect_swapped (remote_contact, "notify",
991 G_CALLBACK (chat_window_update_chat_tab),
994 if (old_remote_contact) {
995 g_signal_handlers_disconnect_by_func (old_remote_contact,
996 chat_window_update_chat_tab,
1000 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1001 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1004 chat_window_update_chat_tab (chat);
1006 window = chat_window_find_chat (chat);
1007 if (window != NULL) {
1008 chat_window_update (window, FALSE);
1013 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1014 EmpathySmiley *smiley,
1017 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1019 GtkTextBuffer *buffer;
1022 chat = priv->current_chat;
1024 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1025 gtk_text_buffer_get_end_iter (buffer, &iter);
1026 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1030 chat_window_conv_activate_cb (GtkAction *action,
1031 EmpathyChatWindow *window)
1033 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1036 EmpathyContact *remote_contact = NULL;
1038 /* Favorite room menu */
1039 is_room = empathy_chat_is_room (priv->current_chat);
1043 gboolean found = FALSE;
1044 EmpathyChatroom *chatroom;
1046 room = empathy_chat_get_id (priv->current_chat);
1047 account = empathy_chat_get_account (priv->current_chat);
1048 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1050 if (chatroom != NULL)
1051 found = empathy_chatroom_is_favorite (chatroom);
1053 DEBUG ("This room %s favorite", found ? "is" : "is not");
1054 gtk_toggle_action_set_active (
1055 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1057 if (chatroom != NULL)
1058 found = empathy_chatroom_is_always_urgent (chatroom);
1060 gtk_toggle_action_set_active (
1061 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1064 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1065 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1067 /* Show contacts menu */
1068 g_object_get (priv->current_chat,
1069 "remote-contact", &remote_contact,
1070 "show-contacts", &active,
1072 if (remote_contact == NULL) {
1073 gtk_toggle_action_set_active (
1074 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1077 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1078 (remote_contact == NULL));
1079 if (remote_contact != NULL) {
1080 g_object_unref (remote_contact);
1085 chat_window_clear_activate_cb (GtkAction *action,
1086 EmpathyChatWindow *window)
1088 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1090 empathy_chat_clear (priv->current_chat);
1094 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1095 EmpathyChatWindow *window)
1097 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1102 EmpathyChatroom *chatroom;
1104 active = gtk_toggle_action_get_active (toggle_action);
1105 account = empathy_chat_get_account (priv->current_chat);
1106 room = empathy_chat_get_id (priv->current_chat);
1107 name = empathy_chat_dup_name (priv->current_chat);
1109 chatroom = empathy_chatroom_manager_ensure_chatroom (
1110 priv->chatroom_manager,
1115 empathy_chatroom_set_favorite (chatroom, active);
1116 g_object_unref (chatroom);
1121 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1122 EmpathyChatWindow *window)
1124 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1129 EmpathyChatroom *chatroom;
1131 active = gtk_toggle_action_get_active (toggle_action);
1132 account = empathy_chat_get_account (priv->current_chat);
1133 room = empathy_chat_get_id (priv->current_chat);
1134 name = empathy_chat_dup_name (priv->current_chat);
1136 chatroom = empathy_chatroom_manager_ensure_chatroom (
1137 priv->chatroom_manager,
1142 empathy_chatroom_set_always_urgent (chatroom, active);
1143 g_object_unref (chatroom);
1148 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1149 EmpathyChatWindow *window)
1151 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1154 active = gtk_toggle_action_get_active (toggle_action);
1156 empathy_chat_set_show_contacts (priv->current_chat, active);
1160 chat_window_invite_participant_activate_cb (GtkAction *action,
1161 EmpathyChatWindow *window)
1163 EmpathyChatWindowPriv *priv;
1165 EmpathyTpChat *tp_chat;
1168 priv = GET_PRIV (window);
1170 g_return_if_fail (priv->current_chat != NULL);
1172 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1174 dialog = empathy_invite_participant_dialog_new (
1175 GTK_WINDOW (priv->dialog), tp_chat);
1176 gtk_widget_show (dialog);
1178 response = gtk_dialog_run (GTK_DIALOG (dialog));
1180 if (response == GTK_RESPONSE_ACCEPT) {
1181 TpContact *tp_contact;
1182 EmpathyContact *contact;
1184 tp_contact = empathy_invite_participant_dialog_get_selected (
1185 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1186 if (tp_contact == NULL) goto out;
1188 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1190 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1192 g_object_unref (contact);
1196 gtk_widget_destroy (dialog);
1200 chat_window_close_activate_cb (GtkAction *action,
1201 EmpathyChatWindow *window)
1203 EmpathyChatWindowPriv *priv;
1205 priv = GET_PRIV (window);
1207 g_return_if_fail (priv->current_chat != NULL);
1209 maybe_close_chat (window, priv->current_chat);
1213 chat_window_edit_activate_cb (GtkAction *action,
1214 EmpathyChatWindow *window)
1216 EmpathyChatWindowPriv *priv;
1217 GtkClipboard *clipboard;
1218 GtkTextBuffer *buffer;
1219 gboolean text_available;
1221 priv = GET_PRIV (window);
1223 g_return_if_fail (priv->current_chat != NULL);
1225 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1226 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1227 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1228 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1232 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1233 if (gtk_text_buffer_get_has_selection (buffer)) {
1234 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1235 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1239 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1241 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1242 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1245 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1246 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1247 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1251 chat_window_cut_activate_cb (GtkAction *action,
1252 EmpathyChatWindow *window)
1254 EmpathyChatWindowPriv *priv;
1256 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1258 priv = GET_PRIV (window);
1260 empathy_chat_cut (priv->current_chat);
1264 chat_window_copy_activate_cb (GtkAction *action,
1265 EmpathyChatWindow *window)
1267 EmpathyChatWindowPriv *priv;
1269 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1271 priv = GET_PRIV (window);
1273 empathy_chat_copy (priv->current_chat);
1277 chat_window_paste_activate_cb (GtkAction *action,
1278 EmpathyChatWindow *window)
1280 EmpathyChatWindowPriv *priv;
1282 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1284 priv = GET_PRIV (window);
1286 empathy_chat_paste (priv->current_chat);
1290 chat_window_find_activate_cb (GtkAction *action,
1291 EmpathyChatWindow *window)
1293 EmpathyChatWindowPriv *priv;
1295 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1297 priv = GET_PRIV (window);
1299 empathy_chat_find (priv->current_chat);
1303 chat_window_tabs_next_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_ == (numPages - 1) && wrap_around) {
1319 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1323 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1327 chat_window_tabs_previous_activate_cb (GtkAction *action,
1328 EmpathyChatWindow *window)
1330 EmpathyChatWindowPriv *priv;
1331 gint index_, numPages;
1332 gboolean wrap_around;
1334 priv = GET_PRIV (window);
1336 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1337 &wrap_around, NULL);
1339 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1340 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1342 if (index_ <= 0 && wrap_around) {
1343 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1347 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1351 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1352 EmpathyChatWindow *window)
1354 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1355 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1356 empathy_get_current_action_time ());
1360 chat_window_tabs_left_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));
1375 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1379 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1380 chat_window_menu_context_update (priv, num_pages);
1384 chat_window_tabs_right_activate_cb (GtkAction *action,
1385 EmpathyChatWindow *window)
1387 EmpathyChatWindowPriv *priv;
1389 gint index_, num_pages;
1391 priv = GET_PRIV (window);
1393 chat = priv->current_chat;
1394 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1396 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1400 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1401 chat_window_menu_context_update (priv, num_pages);
1404 static EmpathyChatWindow *
1405 empathy_chat_window_new (void)
1407 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1411 chat_window_detach_activate_cb (GtkAction *action,
1412 EmpathyChatWindow *window)
1414 EmpathyChatWindowPriv *priv;
1415 EmpathyChatWindow *new_window;
1418 priv = GET_PRIV (window);
1420 chat = priv->current_chat;
1421 new_window = empathy_chat_window_new ();
1423 empathy_chat_window_move_chat (window, new_window, chat);
1425 priv = GET_PRIV (new_window);
1426 gtk_widget_show (priv->dialog);
1430 chat_window_help_contents_activate_cb (GtkAction *action,
1431 EmpathyChatWindow *window)
1433 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1435 empathy_url_show (priv->dialog, "help:empathy");
1439 chat_window_help_about_activate_cb (GtkAction *action,
1440 EmpathyChatWindow *window)
1442 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1444 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1448 chat_window_delete_event_cb (GtkWidget *dialog,
1450 EmpathyChatWindow *window)
1452 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1453 EmpathyChat *chat = NULL;
1457 DEBUG ("Delete event received");
1459 for (l = priv->chats; l != NULL; l = l->next) {
1460 if (chat_needs_close_confirmation (l->data)) {
1467 confirm_close (window, TRUE, n_rooms,
1468 (n_rooms == 1 ? chat : NULL));
1470 remove_all_chats (window);
1477 chat_window_composing_cb (EmpathyChat *chat,
1478 gboolean is_composing,
1479 EmpathyChatWindow *window)
1481 chat_window_update_chat_tab (chat);
1485 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1488 EmpathyChatWindowPriv *priv;
1490 priv = GET_PRIV (window);
1492 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1496 chat_window_notification_closed_cb (NotifyNotification *notify,
1497 EmpathyChatWindow *self)
1499 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1501 g_object_unref (notify);
1502 if (priv->notification == notify) {
1503 priv->notification = NULL;
1508 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1509 EmpathyMessage *message,
1512 EmpathyContact *sender;
1513 const gchar *header;
1517 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1518 gboolean res, has_x_canonical_append;
1519 NotifyNotification *notification = priv->notification;
1521 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1524 res = g_settings_get_boolean (priv->gsettings_notif,
1525 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1532 sender = empathy_message_get_sender (message);
1533 header = empathy_contact_get_alias (sender);
1534 body = empathy_message_get_body (message);
1535 escaped = g_markup_escape_text (body, -1);
1536 has_x_canonical_append = empathy_notify_manager_has_capability (
1537 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1539 if (notification != NULL && !has_x_canonical_append) {
1540 /* if the notification server supports x-canonical-append, it is
1541 better to not use notify_notification_update to avoid
1542 overwriting the current notification message */
1543 notify_notification_update (notification,
1544 header, escaped, NULL);
1546 /* if the notification server supports x-canonical-append,
1547 the hint will be added, so that the message from the
1548 just created notification will be automatically appended
1549 to an existing notification with the same title.
1550 In this way the previous message will not be lost: the new
1551 message will appear below it, in the same notification */
1552 notification = notify_notification_new (header, escaped, NULL);
1554 if (priv->notification == NULL) {
1555 priv->notification = notification;
1558 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1560 tp_g_signal_connect_object (notification, "closed",
1561 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1563 if (has_x_canonical_append) {
1564 /* We have to set a not empty string to keep libnotify happy */
1565 notify_notification_set_hint_string (notification,
1566 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1570 const gchar *category = empathy_chat_is_room (chat)
1571 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1572 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1573 notify_notification_set_hint (notification,
1574 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1575 g_variant_new_string (category));
1579 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1580 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1582 if (pixbuf != NULL) {
1583 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1584 g_object_unref (pixbuf);
1587 notify_notification_show (notification, NULL);
1593 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1595 EmpathyChatWindowPriv *priv;
1598 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1600 priv = GET_PRIV (window);
1602 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1608 chat_window_new_message_cb (EmpathyChat *chat,
1609 EmpathyMessage *message,
1611 gboolean should_highlight,
1612 EmpathyChatWindow *window)
1614 EmpathyChatWindowPriv *priv;
1616 gboolean needs_urgency;
1617 EmpathyContact *sender;
1619 priv = GET_PRIV (window);
1621 has_focus = empathy_chat_window_has_focus (window);
1623 /* - if we're the sender, we play the sound if it's specified in the
1624 * preferences and we're not away.
1625 * - if we receive a message, we play the sound if it's specified in the
1626 * preferences and the window does not have focus on the chat receiving
1630 sender = empathy_message_get_sender (message);
1632 if (empathy_contact_is_user (sender)) {
1633 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1634 EMPATHY_SOUND_MESSAGE_OUTGOING);
1637 if (has_focus && priv->current_chat == chat) {
1638 /* window and tab are focused so consider the message to be read */
1640 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1641 empathy_chat_messages_read (chat);
1645 /* Update the chat tab if this is the first unread message */
1646 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1647 chat_window_update_chat_tab (chat);
1650 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1651 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1652 * an unamed MUC (msn-like).
1653 * In case of a MUC, we set urgency if either:
1654 * a) the chatroom's always_urgent property is TRUE
1655 * b) the message contains our alias
1657 if (empathy_chat_is_room (chat)) {
1660 EmpathyChatroom *chatroom;
1662 account = empathy_chat_get_account (chat);
1663 room = empathy_chat_get_id (chat);
1665 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1668 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1669 needs_urgency = TRUE;
1671 needs_urgency = should_highlight;
1674 needs_urgency = TRUE;
1677 if (needs_urgency) {
1679 chat_window_set_urgency_hint (window, TRUE);
1682 /* Pending messages have already been displayed and notified in the
1683 * approver, so we don't display a notification and play a sound for those */
1685 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1686 EMPATHY_SOUND_MESSAGE_INCOMING);
1688 chat_window_show_or_update_notification (window, message, chat);
1692 /* update the number of unread messages and the window icon */
1693 chat_window_title_update (priv);
1694 chat_window_icon_update (priv, TRUE);
1698 chat_window_command_part (EmpathyChat *chat,
1701 EmpathyChat *chat_to_be_parted;
1702 EmpathyTpChat *tp_chat = NULL;
1704 if (strv[1] == NULL) {
1705 /* No chatroom ID specified */
1706 tp_chat = empathy_chat_get_tp_chat (chat);
1708 empathy_tp_chat_leave (tp_chat, "");
1711 chat_to_be_parted = empathy_chat_window_find_chat (
1712 empathy_chat_get_account (chat), strv[1], FALSE);
1714 if (chat_to_be_parted != NULL) {
1715 /* Found a chatroom matching the specified ID */
1716 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1718 empathy_tp_chat_leave (tp_chat, strv[2]);
1722 /* Going by the syntax of PART command:
1724 * /PART [<chatroom-ID>] [<reason>]
1726 * Chatroom-ID is not a must to specify a reason.
1727 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1728 * MUC then the current chatroom should be parted and srtv[1] should
1729 * be treated as part of the optional part-message. */
1730 message = g_strconcat (strv[1], " ", strv[2], NULL);
1731 tp_chat = empathy_chat_get_tp_chat (chat);
1733 empathy_tp_chat_leave (tp_chat, message);
1739 static GtkNotebook *
1740 notebook_create_window_cb (GtkNotebook *source,
1746 EmpathyChatWindowPriv *priv;
1747 EmpathyChatWindow *window, *new_window;
1750 chat = EMPATHY_CHAT (page);
1751 window = chat_window_find_chat (chat);
1753 new_window = empathy_chat_window_new ();
1754 priv = GET_PRIV (new_window);
1756 DEBUG ("Detach hook called");
1758 empathy_chat_window_move_chat (window, new_window, chat);
1760 gtk_widget_show (priv->dialog);
1761 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1767 chat_window_page_switched_cb (GtkNotebook *notebook,
1770 EmpathyChatWindow *window)
1772 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1773 EmpathyChat *chat = EMPATHY_CHAT (child);
1775 DEBUG ("Page switched");
1777 if (priv->page_added) {
1778 priv->page_added = FALSE;
1779 empathy_chat_scroll_down (chat);
1781 else if (priv->current_chat == chat) {
1785 priv->current_chat = chat;
1786 empathy_chat_messages_read (chat);
1788 chat_window_update_chat_tab (chat);
1792 chat_window_page_added_cb (GtkNotebook *notebook,
1795 EmpathyChatWindow *window)
1797 EmpathyChatWindowPriv *priv;
1800 priv = GET_PRIV (window);
1802 /* If we just received DND to the same window, we don't want
1803 * to do anything here like removing the tab and then readding
1804 * it, so we return here and in "page-added".
1806 if (priv->dnd_same_window) {
1807 DEBUG ("Page added (back to the same window)");
1808 priv->dnd_same_window = FALSE;
1812 DEBUG ("Page added");
1814 /* Get chat object */
1815 chat = EMPATHY_CHAT (child);
1817 /* Connect chat signals for this window */
1818 g_signal_connect (chat, "composing",
1819 G_CALLBACK (chat_window_composing_cb),
1821 g_signal_connect (chat, "new-message",
1822 G_CALLBACK (chat_window_new_message_cb),
1824 g_signal_connect (chat, "part-command-entered",
1825 G_CALLBACK (chat_window_command_part),
1827 g_signal_connect (chat, "notify::tp-chat",
1828 G_CALLBACK (chat_window_update_chat_tab),
1831 /* Set flag so we know to perform some special operations on
1832 * switch page due to the new page being added.
1834 priv->page_added = TRUE;
1836 /* Get list of chats up to date */
1837 priv->chats = g_list_append (priv->chats, chat);
1839 chat_window_update_chat_tab (chat);
1843 chat_window_page_removed_cb (GtkNotebook *notebook,
1846 EmpathyChatWindow *window)
1848 EmpathyChatWindowPriv *priv;
1851 priv = GET_PRIV (window);
1853 /* If we just received DND to the same window, we don't want
1854 * to do anything here like removing the tab and then readding
1855 * it, so we return here and in "page-added".
1857 if (priv->dnd_same_window) {
1858 DEBUG ("Page removed (and will be readded to same window)");
1862 DEBUG ("Page removed");
1864 /* Get chat object */
1865 chat = EMPATHY_CHAT (child);
1867 /* Disconnect all signal handlers for this chat and this window */
1868 g_signal_handlers_disconnect_by_func (chat,
1869 G_CALLBACK (chat_window_composing_cb),
1871 g_signal_handlers_disconnect_by_func (chat,
1872 G_CALLBACK (chat_window_new_message_cb),
1874 g_signal_handlers_disconnect_by_func (chat,
1875 G_CALLBACK (chat_window_update_chat_tab),
1878 /* Keep list of chats up to date */
1879 priv->chats = g_list_remove (priv->chats, chat);
1880 empathy_chat_messages_read (chat);
1882 if (priv->chats == NULL) {
1883 g_object_unref (window);
1885 chat_window_update (window, TRUE);
1890 chat_window_focus_in_event_cb (GtkWidget *widget,
1892 EmpathyChatWindow *window)
1894 EmpathyChatWindowPriv *priv;
1896 priv = GET_PRIV (window);
1898 empathy_chat_messages_read (priv->current_chat);
1900 chat_window_set_urgency_hint (window, FALSE);
1902 /* Update the title, since we now mark all unread messages as read. */
1903 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1909 chat_window_drag_drop (GtkWidget *widget,
1910 GdkDragContext *context,
1914 EmpathyChatWindow *window)
1917 EmpathyChatWindowPriv *priv;
1919 priv = GET_PRIV (window);
1921 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1922 if (target == GDK_NONE)
1923 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1925 if (target != GDK_NONE) {
1926 gtk_drag_get_data (widget, context, target, time_);
1934 chat_window_drag_motion (GtkWidget *widget,
1935 GdkDragContext *context,
1939 EmpathyChatWindow *window)
1942 EmpathyChatWindowPriv *priv;
1944 priv = GET_PRIV (window);
1946 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1947 if (target != GDK_NONE) {
1948 /* This is a file drag. Ensure the contact is online and set the
1949 drag type to COPY. Note that it's possible that the tab will
1950 be switched by GTK+ after a timeout from drag_motion without
1951 getting another drag_motion to disable the drop. You have
1952 to hold your mouse really still.
1954 EmpathyContact *contact;
1956 priv = GET_PRIV (window);
1957 contact = empathy_chat_get_remote_contact (priv->current_chat);
1958 /* contact is NULL for multi-user chats. We don't do
1959 * file transfers to MUCs. We also don't send files
1960 * to offline contacts or contacts that don't support
1963 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1964 gdk_drag_status (context, 0, time_);
1967 if (!(empathy_contact_get_capabilities (contact)
1968 & EMPATHY_CAPABILITIES_FT)) {
1969 gdk_drag_status (context, 0, time_);
1972 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1976 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1977 if (target != GDK_NONE) {
1978 /* This is a drag of a contact from a contact list. Set to COPY.
1979 FIXME: If this drag is to a MUC window, it invites the user.
1980 Otherwise, it opens a chat. Should we use a different drag
1981 type for invites? Should we allow ASK?
1983 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1991 drag_data_received_individual_id (EmpathyChatWindow *self,
1993 GdkDragContext *context,
1996 GtkSelectionData *selection,
2001 EmpathyIndividualManager *manager = NULL;
2002 FolksIndividual *individual;
2003 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2004 EmpathyTpChat *chat;
2005 TpContact *tp_contact;
2007 EmpathyContact *contact;
2009 id = (const gchar *) gtk_selection_data_get_data (selection);
2011 DEBUG ("DND invididual %s", id);
2013 if (priv->current_chat == NULL)
2016 chat = empathy_chat_get_tp_chat (priv->current_chat);
2020 if (!empathy_tp_chat_can_add_contact (chat)) {
2021 DEBUG ("Can't invite contact to %s",
2022 tp_proxy_get_object_path (chat));
2026 manager = empathy_individual_manager_dup_singleton ();
2028 individual = empathy_individual_manager_lookup_member (manager, id);
2029 if (individual == NULL) {
2030 DEBUG ("Failed to find individual %s", id);
2034 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2035 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2036 if (tp_contact == NULL) {
2037 DEBUG ("Can't find a TpContact on connection %s for %s",
2038 tp_proxy_get_object_path (conn), id);
2042 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2043 tp_channel_get_identifier ((TpChannel *) chat));
2045 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2046 empathy_tp_chat_add (chat, contact, NULL);
2047 g_object_unref (contact);
2050 gtk_drag_finish (context, TRUE, FALSE, time_);
2051 tp_clear_object (&manager);
2055 chat_window_drag_data_received (GtkWidget *widget,
2056 GdkDragContext *context,
2059 GtkSelectionData *selection,
2062 EmpathyChatWindow *window)
2064 if (info == DND_DRAG_TYPE_CONTACT_ID) {
2065 EmpathyChat *chat = NULL;
2066 EmpathyChatWindow *old_window;
2067 TpAccount *account = NULL;
2068 EmpathyClientFactory *factory;
2071 const gchar *account_id;
2072 const gchar *contact_id;
2074 id = (const gchar*) gtk_selection_data_get_data (selection);
2076 factory = empathy_client_factory_dup ();
2078 DEBUG ("DND contact from roster with id:'%s'", id);
2080 strv = g_strsplit (id, ":", 2);
2081 if (g_strv_length (strv) == 2) {
2082 account_id = strv[0];
2083 contact_id = strv[1];
2085 tp_simple_client_factory_ensure_account (
2086 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2089 g_object_unref (factory);
2090 if (account != NULL)
2091 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2094 if (account == NULL) {
2096 gtk_drag_finish (context, FALSE, FALSE, time_);
2101 empathy_chat_with_contact_id (
2102 account, contact_id,
2103 empathy_get_current_action_time (),
2111 old_window = chat_window_find_chat (chat);
2113 if (old_window == window) {
2114 gtk_drag_finish (context, TRUE, FALSE, time_);
2118 empathy_chat_window_move_chat (old_window, window, chat);
2120 empathy_chat_window_add_chat (window, chat);
2123 /* Added to take care of any outstanding chat events */
2124 empathy_chat_window_present_chat (chat,
2125 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2127 /* We should return TRUE to remove the data when doing
2128 * GDK_ACTION_MOVE, but we don't here otherwise it has
2129 * weird consequences, and we handle that internally
2130 * anyway with add_chat () and remove_chat ().
2132 gtk_drag_finish (context, TRUE, FALSE, time_);
2134 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2135 drag_data_received_individual_id (window, widget, context, x, y,
2136 selection, info, time_);
2138 else if (info == DND_DRAG_TYPE_URI_LIST) {
2139 EmpathyChatWindowPriv *priv;
2140 EmpathyContact *contact;
2143 priv = GET_PRIV (window);
2144 contact = empathy_chat_get_remote_contact (priv->current_chat);
2146 /* contact is NULL when current_chat is a multi-user chat.
2147 * We don't do file transfers to MUCs, so just cancel the drag.
2149 if (contact == NULL) {
2150 gtk_drag_finish (context, TRUE, FALSE, time_);
2154 data = (const gchar *) gtk_selection_data_get_data (selection);
2155 empathy_send_file_from_uri_list (contact, data);
2157 gtk_drag_finish (context, TRUE, FALSE, time_);
2159 else if (info == DND_DRAG_TYPE_TAB) {
2161 EmpathyChatWindow *old_window = NULL;
2165 chat = (void *) gtk_selection_data_get_data (selection);
2166 old_window = chat_window_find_chat (*chat);
2169 EmpathyChatWindowPriv *priv;
2171 priv = GET_PRIV (window);
2172 priv->dnd_same_window = (old_window == window);
2173 DEBUG ("DND tab (within same window: %s)",
2174 priv->dnd_same_window ? "Yes" : "No");
2177 DEBUG ("DND from unknown source");
2178 gtk_drag_finish (context, FALSE, FALSE, time_);
2183 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2184 guint num_chats_in_manager,
2185 EmpathyChatWindow *window)
2187 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2189 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2190 num_chats_in_manager > 0);
2194 chat_window_finalize (GObject *object)
2196 EmpathyChatWindow *window;
2197 EmpathyChatWindowPriv *priv;
2199 window = EMPATHY_CHAT_WINDOW (object);
2200 priv = GET_PRIV (window);
2202 DEBUG ("Finalized: %p", object);
2204 g_object_unref (priv->ui_manager);
2205 g_object_unref (priv->chatroom_manager);
2206 g_object_unref (priv->notify_mgr);
2207 g_object_unref (priv->gsettings_chat);
2208 g_object_unref (priv->gsettings_notif);
2209 g_object_unref (priv->gsettings_ui);
2210 g_object_unref (priv->sound_mgr);
2212 if (priv->notification != NULL) {
2213 notify_notification_close (priv->notification, NULL);
2214 priv->notification = NULL;
2217 if (priv->contact_targets) {
2218 gtk_target_list_unref (priv->contact_targets);
2220 if (priv->file_targets) {
2221 gtk_target_list_unref (priv->file_targets);
2224 if (priv->chat_manager) {
2225 g_signal_handler_disconnect (priv->chat_manager,
2226 priv->chat_manager_chats_changed_id);
2227 g_object_unref (priv->chat_manager);
2228 priv->chat_manager = NULL;
2231 chat_windows = g_list_remove (chat_windows, window);
2232 gtk_widget_destroy (priv->dialog);
2234 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2238 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2240 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2242 object_class->finalize = chat_window_finalize;
2244 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2248 empathy_chat_window_init (EmpathyChatWindow *window)
2251 GtkAccelGroup *accel_group;
2256 GtkWidget *chat_vbox;
2258 EmpathySmileyManager *smiley_manager;
2259 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2260 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2262 window->priv = priv;
2263 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2264 gui = empathy_builder_get_file (filename,
2265 "chat_window", &priv->dialog,
2266 "chat_vbox", &chat_vbox,
2267 "ui_manager", &priv->ui_manager,
2268 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2269 "menu_conv_favorite", &priv->menu_conv_favorite,
2270 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2271 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2272 "menu_edit_cut", &priv->menu_edit_cut,
2273 "menu_edit_copy", &priv->menu_edit_copy,
2274 "menu_edit_paste", &priv->menu_edit_paste,
2275 "menu_edit_find", &priv->menu_edit_find,
2276 "menu_tabs_next", &priv->menu_tabs_next,
2277 "menu_tabs_prev", &priv->menu_tabs_prev,
2278 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2279 "menu_tabs_left", &priv->menu_tabs_left,
2280 "menu_tabs_right", &priv->menu_tabs_right,
2281 "menu_tabs_detach", &priv->menu_tabs_detach,
2285 empathy_builder_connect (gui, window,
2286 "menu_conv", "activate", chat_window_conv_activate_cb,
2287 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2288 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2289 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2290 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2291 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2292 "menu_conv_close", "activate", chat_window_close_activate_cb,
2293 "menu_edit", "activate", chat_window_edit_activate_cb,
2294 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2295 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2296 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2297 "menu_edit_find", "activate", chat_window_find_activate_cb,
2298 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2299 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2300 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2301 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2302 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2303 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2304 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2305 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2308 g_object_ref (priv->ui_manager);
2309 g_object_unref (gui);
2311 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2312 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2313 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2314 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2316 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2318 priv->notebook = gtk_notebook_new ();
2320 g_signal_connect (priv->notebook, "create-window",
2321 G_CALLBACK (notebook_create_window_cb), window);
2323 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2324 "EmpathyChatWindow");
2325 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2326 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2327 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2328 gtk_widget_show (priv->notebook);
2331 accel_group = gtk_accel_group_new ();
2332 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2334 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2335 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2338 gtk_accel_group_connect (accel_group,
2345 g_object_unref (accel_group);
2347 /* Set up drag target lists */
2348 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2349 G_N_ELEMENTS (drag_types_dest_contact));
2350 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2351 G_N_ELEMENTS (drag_types_dest_file));
2353 /* Set up smiley menu */
2354 smiley_manager = empathy_smiley_manager_dup_singleton ();
2355 submenu = empathy_smiley_menu_new (smiley_manager,
2356 chat_window_insert_smiley_activate_cb,
2358 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2359 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2360 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2361 g_object_unref (smiley_manager);
2363 /* Set up signals we can't do with ui file since we may need to
2364 * block/unblock them at some later stage.
2367 g_signal_connect (priv->dialog,
2369 G_CALLBACK (chat_window_delete_event_cb),
2371 g_signal_connect (priv->dialog,
2373 G_CALLBACK (chat_window_focus_in_event_cb),
2375 g_signal_connect_after (priv->notebook,
2377 G_CALLBACK (chat_window_page_switched_cb),
2379 g_signal_connect (priv->notebook,
2381 G_CALLBACK (chat_window_page_added_cb),
2383 g_signal_connect (priv->notebook,
2385 G_CALLBACK (chat_window_page_removed_cb),
2388 /* Set up drag and drop */
2389 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2390 GTK_DEST_DEFAULT_HIGHLIGHT,
2392 G_N_ELEMENTS (drag_types_dest),
2393 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2395 /* connect_after to allow GtkNotebook's built-in tab switching */
2396 g_signal_connect_after (priv->notebook,
2398 G_CALLBACK (chat_window_drag_motion),
2400 g_signal_connect (priv->notebook,
2401 "drag-data-received",
2402 G_CALLBACK (chat_window_drag_data_received),
2404 g_signal_connect (priv->notebook,
2406 G_CALLBACK (chat_window_drag_drop),
2409 chat_windows = g_list_prepend (chat_windows, window);
2411 /* Set up private details */
2413 priv->current_chat = NULL;
2414 priv->notification = NULL;
2416 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2418 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2419 priv->chat_manager_chats_changed_id =
2420 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2421 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2424 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2425 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2429 /* Returns the window to open a new tab in if there is a suitable window,
2430 * otherwise, returns NULL indicating that a new window should be added.
2432 static EmpathyChatWindow *
2433 empathy_chat_window_get_default (gboolean room)
2435 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2437 gboolean separate_windows = TRUE;
2439 separate_windows = g_settings_get_boolean (gsettings,
2440 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2442 g_object_unref (gsettings);
2444 if (separate_windows) {
2445 /* Always create a new window */
2449 for (l = chat_windows; l; l = l->next) {
2450 EmpathyChatWindow *chat_window;
2451 guint nb_rooms, nb_private;
2453 chat_window = l->data;
2455 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2457 /* Skip the window if there aren't any rooms in it */
2458 if (room && nb_rooms == 0)
2461 /* Skip the window if there aren't any 1-1 chats in it */
2462 if (!room && nb_private == 0)
2472 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2475 EmpathyChatWindowPriv *priv;
2477 GtkWidget *popup_label;
2479 GValue value = { 0, };
2481 g_return_if_fail (window != NULL);
2482 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2484 priv = GET_PRIV (window);
2486 /* Reference the chat object */
2487 g_object_ref (chat);
2489 /* If this window has just been created, position it */
2490 if (priv->chats == NULL) {
2491 const gchar *name = "chat-window";
2492 gboolean separate_windows;
2494 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2495 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2497 if (empathy_chat_is_room (chat))
2498 name = "room-window";
2500 if (separate_windows) {
2503 /* Save current position of the window */
2504 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2506 /* First bind to the 'generic' name. So new window for which we didn't
2507 * save a geometry yet will have the geometry of the last saved
2508 * window (bgo #601191). */
2509 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2511 /* Restore previous position of the window so the newly created window
2512 * won't be in the same position as the latest saved window and so
2513 * completely hide it. */
2514 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2516 /* Then bind it to the name of the contact/room so we'll save the
2517 * geometry specific to this window */
2518 name = empathy_chat_get_id (chat);
2521 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2524 child = GTK_WIDGET (chat);
2525 label = chat_window_create_label (window, chat, TRUE);
2526 popup_label = chat_window_create_label (window, chat, FALSE);
2527 gtk_widget_show (child);
2529 g_signal_connect (chat, "notify::name",
2530 G_CALLBACK (chat_window_chat_notify_cb),
2532 g_signal_connect (chat, "notify::subject",
2533 G_CALLBACK (chat_window_chat_notify_cb),
2535 g_signal_connect (chat, "notify::remote-contact",
2536 G_CALLBACK (chat_window_chat_notify_cb),
2538 g_signal_connect (chat, "notify::sms-channel",
2539 G_CALLBACK (chat_window_chat_notify_cb),
2541 g_signal_connect (chat, "notify::n-messages-sending",
2542 G_CALLBACK (chat_window_chat_notify_cb),
2544 g_signal_connect (chat, "notify::nb-unread-messages",
2545 G_CALLBACK (chat_window_chat_notify_cb),
2547 chat_window_chat_notify_cb (chat);
2549 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2550 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2551 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2552 g_value_init (&value, G_TYPE_BOOLEAN);
2553 g_value_set_boolean (&value, TRUE);
2554 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2555 child, "tab-expand" , &value);
2556 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2557 child, "tab-fill" , &value);
2558 g_value_unset (&value);
2560 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2564 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2567 EmpathyChatWindowPriv *priv;
2569 EmpathyContact *remote_contact;
2570 EmpathyChatManager *chat_manager;
2572 g_return_if_fail (window != NULL);
2573 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2575 priv = GET_PRIV (window);
2577 g_signal_handlers_disconnect_by_func (chat,
2578 chat_window_chat_notify_cb,
2580 remote_contact = g_object_get_data (G_OBJECT (chat),
2581 "chat-window-remote-contact");
2582 if (remote_contact) {
2583 g_signal_handlers_disconnect_by_func (remote_contact,
2584 chat_window_update_chat_tab,
2588 chat_manager = empathy_chat_manager_dup_singleton ();
2589 empathy_chat_manager_closed_chat (chat_manager, chat);
2590 g_object_unref (chat_manager);
2592 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2594 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2596 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2598 g_object_unref (chat);
2602 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2603 EmpathyChatWindow *new_window,
2608 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2609 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2610 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2612 widget = GTK_WIDGET (chat);
2614 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2615 G_OBJECT (widget)->ref_count);
2617 /* We reference here to make sure we don't loose the widget
2618 * and the EmpathyChat object during the move.
2620 g_object_ref (chat);
2621 g_object_ref (widget);
2623 empathy_chat_window_remove_chat (old_window, chat);
2624 empathy_chat_window_add_chat (new_window, chat);
2626 g_object_unref (widget);
2627 g_object_unref (chat);
2631 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2634 EmpathyChatWindowPriv *priv;
2637 g_return_if_fail (window != NULL);
2638 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2640 priv = GET_PRIV (window);
2642 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2644 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2649 empathy_chat_window_find_chat (TpAccount *account,
2651 gboolean sms_channel)
2655 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2657 for (l = chat_windows; l; l = l->next) {
2658 EmpathyChatWindowPriv *priv;
2659 EmpathyChatWindow *window;
2663 priv = GET_PRIV (window);
2665 for (ll = priv->chats; ll; ll = ll->next) {
2670 if (account == empathy_chat_get_account (chat) &&
2671 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2672 sms_channel == empathy_chat_is_sms_channel (chat)) {
2682 empathy_chat_window_present_chat (EmpathyChat *chat,
2685 EmpathyChatWindow *window;
2686 EmpathyChatWindowPriv *priv;
2687 guint32 x_timestamp;
2689 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2691 window = chat_window_find_chat (chat);
2693 /* If the chat has no window, create one */
2694 if (window == NULL) {
2695 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2697 window = empathy_chat_window_new ();
2699 /* we want to display the newly created window even if we don't present
2701 priv = GET_PRIV (window);
2702 gtk_widget_show (priv->dialog);
2705 empathy_chat_window_add_chat (window, chat);
2708 /* Don't force the window to show itself when it wasn't
2709 * an action by the user
2711 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2714 priv = GET_PRIV (window);
2716 if (x_timestamp != GDK_CURRENT_TIME) {
2717 /* Don't present or switch tab if the action was earlier than the
2718 * last actions X time, accounting for overflow and the first ever
2721 if (priv->x_user_action_time != 0
2722 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2725 priv->x_user_action_time = x_timestamp;
2728 empathy_chat_window_switch_to_chat (window, chat);
2730 /* Don't use empathy_window_present_with_time () which would move the window
2731 * to our current desktop but move to the window's desktop instead. This is
2732 * more coherent with Shell's 'app is ready' notication which moves the view
2733 * to the app desktop rather than moving the app itself. */
2734 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2736 gtk_widget_grab_focus (chat->input_text_view);
2740 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2744 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2746 guint _nb_rooms = 0, _nb_private = 0;
2748 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2749 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2755 if (nb_rooms != NULL)
2756 *nb_rooms = _nb_rooms;
2757 if (nb_private != NULL)
2758 *nb_private = _nb_private;