2 * Copyright (C) 2003-2007 Imendio AB
3 * Copyright (C) 2007-2012 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Richard Hult <richard@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24 * Xavier Claessens <xclaesse@gmail.com>
25 * Rômulo Fernandes Machado <romulo@castorgroup.net>
33 #include <gdk/gdkkeysyms.h>
35 #include <glib/gi18n.h>
36 #include <libnotify/notification.h>
38 #include <telepathy-glib/telepathy-glib.h>
40 #include <libempathy/empathy-client-factory.h>
41 #include <libempathy/empathy-contact.h>
42 #include <libempathy/empathy-message.h>
43 #include <libempathy/empathy-chatroom-manager.h>
44 #include <libempathy/empathy-gsettings.h>
45 #include <libempathy/empathy-utils.h>
46 #include <libempathy/empathy-request-util.h>
47 #include <libempathy/empathy-individual-manager.h>
49 #include <libempathy-gtk/empathy-images.h>
50 #include <libempathy-gtk/empathy-log-window.h>
51 #include <libempathy-gtk/empathy-geometry.h>
52 #include <libempathy-gtk/empathy-smiley-manager.h>
53 #include <libempathy-gtk/empathy-sound-manager.h>
54 #include <libempathy-gtk/empathy-ui-utils.h>
55 #include <libempathy-gtk/empathy-notify-manager.h>
57 #include "empathy-chat-manager.h"
58 #include "empathy-chat-window.h"
59 #include "empathy-about-dialog.h"
60 #include "empathy-invite-participant-dialog.h"
62 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
63 #include <libempathy/empathy-debug.h>
65 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
67 #define X_EARLIER_OR_EQL(t1, t2) \
68 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
69 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
72 struct _EmpathyChatWindowPriv
74 EmpathyChat *current_chat;
77 gboolean dnd_same_window;
78 EmpathyChatroomManager *chatroom_manager;
79 EmpathyNotifyManager *notify_mgr;
82 NotifyNotification *notification;
84 GtkTargetList *contact_targets;
85 GtkTargetList *file_targets;
87 EmpathyChatManager *chat_manager;
88 gulong chat_manager_chats_changed_id;
91 GtkUIManager *ui_manager;
92 GtkAction *menu_conv_insert_smiley;
93 GtkAction *menu_conv_favorite;
94 GtkAction *menu_conv_always_urgent;
95 GtkAction *menu_conv_toggle_contacts;
97 GtkAction *menu_edit_cut;
98 GtkAction *menu_edit_copy;
99 GtkAction *menu_edit_paste;
100 GtkAction *menu_edit_find;
102 GtkAction *menu_tabs_next;
103 GtkAction *menu_tabs_prev;
104 GtkAction *menu_tabs_undo_close_tab;
105 GtkAction *menu_tabs_left;
106 GtkAction *menu_tabs_right;
107 GtkAction *menu_tabs_detach;
109 /* Last user action time we acted upon to show a tab */
110 guint32 x_user_action_time;
112 GSettings *gsettings_chat;
113 GSettings *gsettings_notif;
114 GSettings *gsettings_ui;
116 EmpathySoundManager *sound_mgr;
119 static GList *chat_windows = NULL;
121 static const guint tab_accel_keys[] =
123 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
124 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
129 DND_DRAG_TYPE_CONTACT_ID,
130 DND_DRAG_TYPE_INDIVIDUAL_ID,
131 DND_DRAG_TYPE_URI_LIST,
135 static const GtkTargetEntry drag_types_dest[] =
137 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
138 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
139 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
140 /* FIXME: disabled because of bug #640513
141 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
142 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
146 static const GtkTargetEntry drag_types_dest_contact[] =
148 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
149 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
152 static const GtkTargetEntry drag_types_dest_file[] =
154 /* must be first to be prioritized, in order to receive the
155 * note's file path from Tomboy instead of an URI */
156 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
157 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
160 static void chat_window_update (EmpathyChatWindow *window,
161 gboolean update_contact_menu);
163 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
166 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
169 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
170 EmpathyChatWindow *new_window,
173 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
177 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT)
180 chat_window_accel_cb (GtkAccelGroup *accelgroup,
184 EmpathyChatWindow *self)
189 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
191 if (tab_accel_keys[i] == key)
199 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
202 static EmpathyChatWindow *
203 chat_window_find_chat (EmpathyChat *chat)
207 for (l = chat_windows; l; l = l->next)
209 EmpathyChatWindow *window = l->data;
211 ll = g_list_find (window->priv->chats, chat);
220 remove_all_chats (EmpathyChatWindow *self)
224 while (self->priv->chats)
225 empathy_chat_window_remove_chat (self, self->priv->chats->data);
227 g_object_unref (self);
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);
251 confirm_close (EmpathyChatWindow *self,
252 gboolean close_window,
257 gchar *primary, *secondary;
259 g_return_if_fail (n_rooms > 0);
262 g_return_if_fail (chat == NULL);
264 g_return_if_fail (chat != NULL);
266 /* If there are no chats in this window, how could we possibly have got
269 g_return_if_fail (self->priv->chats != NULL);
271 /* Treat closing a window which only has one tab exactly like closing
274 if (close_window && self->priv->chats->next == NULL)
276 close_window = FALSE;
277 chat = self->priv->chats->data;
282 primary = g_strdup (_("Close this window?"));
286 gchar *chat_name = empathy_chat_dup_name (chat);
287 secondary = g_strdup_printf (
288 _("Closing this window will leave %s. You will "
289 "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.",
311 gchar *chat_name = empathy_chat_dup_name (chat);
312 primary = g_strdup_printf (_("Leave %s?"), chat_name);
313 secondary = g_strdup (
314 _("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 (self->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);
341 g_signal_connect (dialog, "response",
342 G_CALLBACK (confirm_close_response_cb), self);
344 gtk_window_present (GTK_WINDOW (dialog));
347 /* Returns TRUE if we should check if the user really wants to leave. If it's
348 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
349 * the user is actually in the room as opposed to having been kicked or gone
350 * offline or something), then we should check.
353 chat_needs_close_confirmation (EmpathyChat *chat)
355 return (empathy_chat_is_room (chat) &&
356 empathy_chat_get_tp_chat (chat) != NULL);
360 maybe_close_chat (EmpathyChatWindow *window,
363 g_return_if_fail (chat != NULL);
365 if (chat_needs_close_confirmation (chat))
366 confirm_close (window, FALSE, 1, chat);
368 empathy_chat_window_remove_chat (window, chat);
372 chat_window_close_clicked_cb (GtkAction *action,
375 EmpathyChatWindow *window;
377 window = chat_window_find_chat (chat);
378 maybe_close_chat (window, chat);
382 chat_tab_style_updated_cb (GtkWidget *hbox,
386 int char_width, h, w;
387 PangoContext *context;
388 const PangoFontDescription *font_desc;
389 PangoFontMetrics *metrics;
391 button = g_object_get_data (G_OBJECT (user_data),
392 "chat-window-tab-close-button");
393 context = gtk_widget_get_pango_context (hbox);
395 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
396 GTK_STATE_FLAG_NORMAL);
398 metrics = pango_context_get_metrics (context, font_desc,
399 pango_context_get_language (context));
400 char_width = pango_font_metrics_get_approximate_char_width (metrics);
401 pango_font_metrics_unref (metrics);
403 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
404 GTK_ICON_SIZE_MENU, &w, &h);
406 /* Request at least about 12 chars width plus at least space for the status
407 * image and the close button */
408 gtk_widget_set_size_request (hbox,
409 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
411 gtk_widget_set_size_request (button, w, h);
415 create_close_button (void)
417 GtkWidget *button, *image;
418 GtkStyleContext *context;
420 button = gtk_button_new ();
422 context = gtk_widget_get_style_context (button);
423 gtk_style_context_add_class (context, "empathy-tab-close-button");
425 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
426 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
428 /* We don't want focus/keynav for the button to avoid clutter, and
429 * Ctrl-W works anyway.
431 gtk_widget_set_can_focus (button, FALSE);
432 gtk_widget_set_can_default (button, FALSE);
434 image = gtk_image_new_from_icon_name ("window-close-symbolic",
436 gtk_widget_show (image);
438 gtk_container_add (GTK_CONTAINER (button), image);
444 chat_window_create_label (EmpathyChatWindow *window,
446 gboolean is_tab_label)
449 GtkWidget *name_label;
450 GtkWidget *status_image;
451 GtkWidget *event_box;
452 GtkWidget *event_box_hbox;
453 PangoAttrList *attr_list;
454 PangoAttribute *attr;
456 /* The spacing between the button and the label. */
457 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
459 event_box = gtk_event_box_new ();
460 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
462 name_label = gtk_label_new (NULL);
464 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
466 attr_list = pango_attr_list_new ();
467 attr = pango_attr_scale_new (1/1.2);
468 attr->start_index = 0;
469 attr->end_index = -1;
470 pango_attr_list_insert (attr_list, attr);
471 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
472 pango_attr_list_unref (attr_list);
474 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
475 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
476 g_object_set_data (G_OBJECT (chat),
477 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
480 status_image = gtk_image_new ();
482 /* Spacing between the icon and label. */
483 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
485 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
486 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
488 g_object_set_data (G_OBJECT (chat),
489 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
491 g_object_set_data (G_OBJECT (chat),
492 is_tab_label ? "chat-window-tab-tooltip-widget" :
493 "chat-window-menu-tooltip-widget",
496 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
497 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
501 GtkWidget *close_button;
502 GtkWidget *sending_spinner;
504 sending_spinner = gtk_spinner_new ();
506 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
508 g_object_set_data (G_OBJECT (chat),
509 "chat-window-tab-sending-spinner",
512 close_button = create_close_button ();
513 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
516 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
518 g_signal_connect (close_button,
520 G_CALLBACK (chat_window_close_clicked_cb), chat);
522 /* React to theme changes and also setup the size correctly. */
523 g_signal_connect (hbox, "style-updated",
524 G_CALLBACK (chat_tab_style_updated_cb), chat);
527 gtk_widget_show_all (hbox);
533 _submenu_notify_visible_changed_cb (GObject *object,
537 g_signal_handlers_disconnect_by_func (object,
538 _submenu_notify_visible_changed_cb, userdata);
540 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
544 chat_window_menu_context_update (EmpathyChatWindow *self,
549 gboolean wrap_around;
550 gboolean is_connected;
553 page_num = gtk_notebook_get_current_page (
554 GTK_NOTEBOOK (self->priv->notebook));
555 first_page = (page_num == 0);
556 last_page = (page_num == (num_pages - 1));
557 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
559 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
561 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
563 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
565 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
566 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
567 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
568 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
572 chat_window_conversation_menu_update (EmpathyChatWindow *self)
574 EmpathyTpChat *tp_chat;
575 TpConnection *connection;
577 gboolean sensitive = FALSE;
579 g_return_if_fail (self->priv->current_chat != NULL);
581 action = gtk_ui_manager_get_action (self->priv->ui_manager,
582 "/chats_menubar/menu_conv/menu_conv_invite_participant");
583 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
587 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
589 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
590 (tp_connection_get_status (connection, NULL) ==
591 TP_CONNECTION_STATUS_CONNECTED);
594 gtk_action_set_sensitive (action, sensitive);
598 chat_window_contact_menu_update (EmpathyChatWindow *self,
599 EmpathyChatWindow *window)
601 GtkWidget *menu, *submenu, *orig_submenu;
603 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
604 "/chats_menubar/menu_contact");
605 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
607 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
609 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
613 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
614 g_object_set_data (G_OBJECT (submenu), "window", self->priv->dialog);
616 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
617 gtk_widget_show (menu);
618 gtk_widget_set_sensitive (menu, TRUE);
622 gtk_widget_set_sensitive (menu, FALSE);
627 tp_g_signal_connect_object (orig_submenu,
629 (GCallback)_submenu_notify_visible_changed_cb, window, 0);
634 get_all_unread_messages (EmpathyChatWindow *self)
639 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
640 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
646 get_window_title_name (EmpathyChatWindow *self)
648 gchar *active_name, *ret;
650 guint current_unread_msgs;
652 nb_chats = g_list_length (self->priv->chats);
653 g_assert (nb_chats > 0);
655 active_name = empathy_chat_dup_name (self->priv->current_chat);
657 current_unread_msgs = empathy_chat_get_nb_unread_messages (
658 self->priv->current_chat);
663 if (current_unread_msgs == 0)
664 ret = g_strdup (active_name);
666 ret = g_strdup_printf (ngettext (
668 "%s (%d unread)", current_unread_msgs),
669 active_name, current_unread_msgs);
673 guint nb_others = nb_chats - 1;
674 guint all_unread_msgs;
676 all_unread_msgs = get_all_unread_messages (self);
678 if (all_unread_msgs == 0)
680 /* no unread message */
681 ret = g_strdup_printf (ngettext (
683 "%s (and %u others)", nb_others),
684 active_name, nb_others);
686 else if (all_unread_msgs == current_unread_msgs)
688 /* unread messages are in the current tab */
689 ret = g_strdup_printf (ngettext (
691 "%s (%d unread)", current_unread_msgs),
692 active_name, current_unread_msgs);
694 else if (current_unread_msgs == 0)
696 /* unread messages are in other tabs */
697 ret = g_strdup_printf (ngettext (
698 "%s (%d unread from others)",
699 "%s (%d unread from others)",
701 active_name, all_unread_msgs);
705 /* unread messages are in all the tabs */
706 ret = g_strdup_printf (ngettext (
707 "%s (%d unread from all)",
708 "%s (%d unread from all)",
710 active_name, all_unread_msgs);
714 g_free (active_name);
720 chat_window_title_update (EmpathyChatWindow *self)
724 name = get_window_title_name (self);
725 gtk_window_set_title (GTK_WINDOW (self->priv->dialog), name);
730 chat_window_icon_update (EmpathyChatWindow *self,
731 gboolean new_messages)
734 EmpathyContact *remote_contact;
735 gboolean avatar_in_icon;
738 n_chats = g_list_length (self->priv->chats);
740 /* Update window icon */
743 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog),
744 EMPATHY_IMAGE_MESSAGE);
748 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
749 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
751 if (n_chats == 1 && avatar_in_icon)
753 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
754 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
756 gtk_window_set_icon (GTK_WINDOW (self->priv->dialog), icon);
759 g_object_unref (icon);
763 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog), NULL);
769 chat_window_close_button_update (EmpathyChatWindow *self,
773 GtkWidget *chat_close_button;
778 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
779 chat_close_button = g_object_get_data (G_OBJECT (chat),
780 "chat-window-tab-close-button");
781 gtk_widget_hide (chat_close_button);
785 for (i=0; i<num_pages; i++)
787 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
788 chat_close_button = g_object_get_data (G_OBJECT (chat),
789 "chat-window-tab-close-button");
790 gtk_widget_show (chat_close_button);
796 chat_window_update (EmpathyChatWindow *self,
797 gboolean update_contact_menu)
801 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
803 /* Update Tab menu */
804 chat_window_menu_context_update (self, num_pages);
806 chat_window_conversation_menu_update (self);
808 /* If this update is due to a focus-in event, we know the menu will be
809 the same as when we last left it, so no work to do. Besides, if we
810 swap out the menu on a focus-in, we may confuse any external global
812 if (update_contact_menu)
814 chat_window_contact_menu_update (self, self);
817 chat_window_title_update (self);
819 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
821 chat_window_close_button_update (self, num_pages);
825 append_markup_printf (GString *string,
832 va_start (args, format);
834 tmp = g_markup_vprintf_escaped (format, args);
835 g_string_append (string, tmp);
842 chat_window_update_chat_tab_full (EmpathyChat *chat,
843 gboolean update_contact_menu)
845 EmpathyChatWindow *self;
846 EmpathyContact *remote_contact;
850 const gchar *subject;
851 const gchar *status = NULL;
855 const gchar *icon_name;
856 GtkWidget *tab_image;
857 GtkWidget *menu_image;
858 GtkWidget *sending_spinner;
861 self = chat_window_find_chat (chat);
865 /* Get information */
866 name = empathy_chat_dup_name (chat);
867 account = empathy_chat_get_account (chat);
868 subject = empathy_chat_get_subject (chat);
869 remote_contact = empathy_chat_get_remote_contact (chat);
871 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
873 name, tp_proxy_get_object_path (account), subject, remote_contact);
875 /* Update tab image */
876 if (empathy_chat_get_tp_chat (chat) == NULL)
878 /* No TpChat, we are disconnected */
881 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
883 icon_name = EMPATHY_IMAGE_MESSAGE;
885 else if (remote_contact && empathy_chat_is_composing (chat))
887 icon_name = EMPATHY_IMAGE_TYPING;
889 else if (empathy_chat_is_sms_channel (chat))
891 icon_name = EMPATHY_IMAGE_SMS;
893 else if (remote_contact)
895 icon_name = empathy_icon_name_for_contact (remote_contact);
899 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
902 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
903 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
905 if (icon_name != NULL)
907 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
909 gtk_widget_show (tab_image);
910 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
912 gtk_widget_show (menu_image);
916 gtk_widget_hide (tab_image);
917 gtk_widget_hide (menu_image);
920 /* Update the sending spinner */
921 nb_sending = empathy_chat_get_n_messages_sending (chat);
922 sending_spinner = g_object_get_data (G_OBJECT (chat),
923 "chat-window-tab-sending-spinner");
925 g_object_set (sending_spinner,
926 "active", nb_sending > 0,
927 "visible", nb_sending > 0,
930 /* Update tab tooltip */
931 tooltip = g_string_new (NULL);
935 id = empathy_contact_get_id (remote_contact);
936 status = empathy_contact_get_presence_message (remote_contact);
943 if (empathy_chat_is_sms_channel (chat))
944 append_markup_printf (tooltip, "%s ", _("SMS:"));
946 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
947 id, tp_account_get_display_name (account));
951 char *tmp = g_strdup_printf (
952 ngettext ("Sending %d message",
953 "Sending %d messages",
957 g_string_append (tooltip, "\n");
958 g_string_append (tooltip, tmp);
960 gtk_widget_set_tooltip_text (sending_spinner, tmp);
964 if (!EMP_STR_EMPTY (status))
965 append_markup_printf (tooltip, "\n<i>%s</i>", status);
967 if (!EMP_STR_EMPTY (subject))
968 append_markup_printf (tooltip, "\n<b>%s</b> %s",
969 _("Topic:"), subject);
971 if (remote_contact && empathy_chat_is_composing (chat))
972 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
974 if (remote_contact != NULL)
976 const gchar * const *types;
978 types = empathy_contact_get_client_types (remote_contact);
979 if (types != NULL && !tp_strdiff (types[0], "phone"))
981 /* I'm on a phone ! */
984 name = g_strdup_printf ("☎ %s", name);
989 markup = g_string_free (tooltip, FALSE);
990 widget = g_object_get_data (G_OBJECT (chat),
991 "chat-window-tab-tooltip-widget");
992 gtk_widget_set_tooltip_markup (widget, markup);
994 widget = g_object_get_data (G_OBJECT (chat),
995 "chat-window-menu-tooltip-widget");
996 gtk_widget_set_tooltip_markup (widget, markup);
999 /* Update tab and menu label */
1000 if (empathy_chat_is_highlighted (chat))
1002 markup = g_markup_printf_escaped (
1003 "<span color=\"red\" weight=\"bold\">%s</span>",
1008 markup = g_markup_escape_text (name, -1);
1011 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1012 gtk_label_set_markup (GTK_LABEL (widget), markup);
1013 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1014 gtk_label_set_markup (GTK_LABEL (widget), markup);
1017 /* Update the window if it's the current chat */
1018 if (self->priv->current_chat == chat)
1019 chat_window_update (self, update_contact_menu);
1025 chat_window_update_chat_tab (EmpathyChat *chat)
1027 chat_window_update_chat_tab_full (chat, TRUE);
1031 chat_window_chat_notify_cb (EmpathyChat *chat)
1033 EmpathyChatWindow *window;
1034 EmpathyContact *old_remote_contact;
1035 EmpathyContact *remote_contact = NULL;
1037 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1038 "chat-window-remote-contact");
1039 remote_contact = empathy_chat_get_remote_contact (chat);
1041 if (old_remote_contact != remote_contact)
1043 /* The remote-contact associated with the chat changed, we need
1044 * to keep track of any change of that contact and update the
1045 * window each time. */
1047 g_signal_connect_swapped (remote_contact, "notify",
1048 G_CALLBACK (chat_window_update_chat_tab), chat);
1050 if (old_remote_contact)
1051 g_signal_handlers_disconnect_by_func (old_remote_contact,
1052 chat_window_update_chat_tab, chat);
1054 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1055 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1058 chat_window_update_chat_tab (chat);
1060 window = chat_window_find_chat (chat);
1062 chat_window_update (window, FALSE);
1066 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1067 EmpathySmiley *smiley,
1070 EmpathyChatWindow *self = user_data;
1072 GtkTextBuffer *buffer;
1075 chat = self->priv->current_chat;
1077 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1078 gtk_text_buffer_get_end_iter (buffer, &iter);
1079 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1083 chat_window_conv_activate_cb (GtkAction *action,
1084 EmpathyChatWindow *self)
1088 EmpathyContact *remote_contact = NULL;
1090 /* Favorite room menu */
1091 is_room = empathy_chat_is_room (self->priv->current_chat);
1096 gboolean found = FALSE;
1097 EmpathyChatroom *chatroom;
1099 room = empathy_chat_get_id (self->priv->current_chat);
1100 account = empathy_chat_get_account (self->priv->current_chat);
1101 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1104 if (chatroom != NULL)
1105 found = empathy_chatroom_is_favorite (chatroom);
1107 DEBUG ("This room %s favorite", found ? "is" : "is not");
1108 gtk_toggle_action_set_active (
1109 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1111 if (chatroom != NULL)
1112 found = empathy_chatroom_is_always_urgent (chatroom);
1114 gtk_toggle_action_set_active (
1115 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1118 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1119 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1121 /* Show contacts menu */
1122 g_object_get (self->priv->current_chat,
1123 "remote-contact", &remote_contact,
1124 "show-contacts", &active,
1127 if (remote_contact == NULL)
1129 gtk_toggle_action_set_active (
1130 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1133 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1134 (remote_contact == NULL));
1136 if (remote_contact != NULL)
1137 g_object_unref (remote_contact);
1141 chat_window_clear_activate_cb (GtkAction *action,
1142 EmpathyChatWindow *self)
1144 empathy_chat_clear (self->priv->current_chat);
1148 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1149 EmpathyChatWindow *self)
1155 EmpathyChatroom *chatroom;
1157 active = gtk_toggle_action_get_active (toggle_action);
1158 account = empathy_chat_get_account (self->priv->current_chat);
1159 room = empathy_chat_get_id (self->priv->current_chat);
1160 name = empathy_chat_dup_name (self->priv->current_chat);
1162 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1163 account, room, name);
1165 empathy_chatroom_set_favorite (chatroom, active);
1166 g_object_unref (chatroom);
1171 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1172 EmpathyChatWindow *self)
1178 EmpathyChatroom *chatroom;
1180 active = gtk_toggle_action_get_active (toggle_action);
1181 account = empathy_chat_get_account (self->priv->current_chat);
1182 room = empathy_chat_get_id (self->priv->current_chat);
1183 name = empathy_chat_dup_name (self->priv->current_chat);
1185 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1186 account, room, name);
1188 empathy_chatroom_set_always_urgent (chatroom, active);
1189 g_object_unref (chatroom);
1194 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1195 EmpathyChatWindow *self)
1199 active = gtk_toggle_action_get_active (toggle_action);
1201 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1205 chat_window_invite_participant_activate_cb (GtkAction *action,
1206 EmpathyChatWindow *self)
1209 EmpathyTpChat *tp_chat;
1212 g_return_if_fail (self->priv->current_chat != NULL);
1214 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1216 dialog = empathy_invite_participant_dialog_new (
1217 GTK_WINDOW (self->priv->dialog), tp_chat);
1219 gtk_widget_show (dialog);
1221 response = gtk_dialog_run (GTK_DIALOG (dialog));
1223 if (response == GTK_RESPONSE_ACCEPT)
1225 TpContact *tp_contact;
1226 EmpathyContact *contact;
1228 tp_contact = empathy_invite_participant_dialog_get_selected (
1229 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1230 if (tp_contact == NULL)
1233 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1235 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1237 g_object_unref (contact);
1241 gtk_widget_destroy (dialog);
1245 chat_window_close_activate_cb (GtkAction *action,
1246 EmpathyChatWindow *self)
1248 g_return_if_fail (self->priv->current_chat != NULL);
1250 maybe_close_chat (self, self->priv->current_chat);
1254 chat_window_edit_activate_cb (GtkAction *action,
1255 EmpathyChatWindow *self)
1257 GtkClipboard *clipboard;
1258 GtkTextBuffer *buffer;
1259 gboolean text_available;
1261 g_return_if_fail (self->priv->current_chat != NULL);
1263 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1265 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1266 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1267 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1271 buffer = gtk_text_view_get_buffer (
1272 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1274 if (gtk_text_buffer_get_has_selection (buffer))
1276 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1277 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1283 selection = empathy_theme_adium_get_has_selection (
1284 self->priv->current_chat->view);
1286 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1287 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1290 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1291 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1292 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1296 chat_window_cut_activate_cb (GtkAction *action,
1297 EmpathyChatWindow *self)
1299 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1301 empathy_chat_cut (self->priv->current_chat);
1305 chat_window_copy_activate_cb (GtkAction *action,
1306 EmpathyChatWindow *self)
1308 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1310 empathy_chat_copy (self->priv->current_chat);
1314 chat_window_paste_activate_cb (GtkAction *action,
1315 EmpathyChatWindow *self)
1317 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1319 empathy_chat_paste (self->priv->current_chat);
1323 chat_window_find_activate_cb (GtkAction *action,
1324 EmpathyChatWindow *self)
1326 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1328 empathy_chat_find (self->priv->current_chat);
1332 chat_window_tabs_next_activate_cb (GtkAction *action,
1333 EmpathyChatWindow *self)
1335 gint index_, numPages;
1336 gboolean wrap_around;
1338 g_object_get (gtk_settings_get_default (),
1339 "gtk-keynav-wrap-around", &wrap_around,
1342 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1343 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1345 if (index_ == (numPages - 1) && wrap_around)
1347 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1351 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1355 chat_window_tabs_previous_activate_cb (GtkAction *action,
1356 EmpathyChatWindow *self)
1358 gint index_, numPages;
1359 gboolean wrap_around;
1361 g_object_get (gtk_settings_get_default (),
1362 "gtk-keynav-wrap-around", &wrap_around,
1365 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1366 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1368 if (index_ <= 0 && wrap_around)
1370 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1375 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1379 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1380 EmpathyChatWindow *self)
1382 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1383 empathy_get_current_action_time ());
1387 chat_window_tabs_left_activate_cb (GtkAction *action,
1388 EmpathyChatWindow *self)
1391 gint index_, num_pages;
1393 chat = self->priv->current_chat;
1394 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1398 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1401 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1402 chat_window_menu_context_update (self, num_pages);
1406 chat_window_tabs_right_activate_cb (GtkAction *action,
1407 EmpathyChatWindow *self)
1410 gint index_, num_pages;
1412 chat = self->priv->current_chat;
1413 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1415 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1418 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1419 chat_window_menu_context_update (self, num_pages);
1422 static EmpathyChatWindow *
1423 empathy_chat_window_new (void)
1425 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1429 chat_window_detach_activate_cb (GtkAction *action,
1430 EmpathyChatWindow *self)
1432 EmpathyChatWindow *new_window;
1435 chat = self->priv->current_chat;
1436 new_window = empathy_chat_window_new ();
1438 empathy_chat_window_move_chat (self, new_window, chat);
1440 gtk_widget_show (new_window->priv->dialog);
1444 chat_window_help_contents_activate_cb (GtkAction *action,
1445 EmpathyChatWindow *self)
1447 empathy_url_show (self->priv->dialog, "help:empathy");
1451 chat_window_help_about_activate_cb (GtkAction *action,
1452 EmpathyChatWindow *self)
1454 empathy_about_dialog_new (GTK_WINDOW (self->priv->dialog));
1458 chat_window_delete_event_cb (GtkWidget *dialog,
1460 EmpathyChatWindow *self)
1462 EmpathyChat *chat = NULL;
1466 DEBUG ("Delete event received");
1468 for (l = self->priv->chats; l != NULL; l = l->next)
1470 if (chat_needs_close_confirmation (l->data))
1479 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1483 remove_all_chats (self);
1490 chat_window_composing_cb (EmpathyChat *chat,
1491 gboolean is_composing,
1492 EmpathyChatWindow *self)
1494 chat_window_update_chat_tab (chat);
1498 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1501 gtk_window_set_urgency_hint (GTK_WINDOW (self->priv->dialog), urgent);
1505 chat_window_notification_closed_cb (NotifyNotification *notify,
1506 EmpathyChatWindow *self)
1508 g_object_unref (notify);
1509 if (self->priv->notification == notify)
1510 self->priv->notification = NULL;
1514 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1515 EmpathyMessage *message,
1518 EmpathyContact *sender;
1519 const gchar *header;
1523 gboolean res, has_x_canonical_append;
1524 NotifyNotification *notification = self->priv->notification;
1526 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1529 res = g_settings_get_boolean (self->priv->gsettings_notif,
1530 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1535 sender = empathy_message_get_sender (message);
1536 header = empathy_contact_get_alias (sender);
1537 body = empathy_message_get_body (message);
1538 escaped = g_markup_escape_text (body, -1);
1540 has_x_canonical_append = empathy_notify_manager_has_capability (
1541 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1543 if (notification != NULL && !has_x_canonical_append)
1545 /* if the notification server supports x-canonical-append, it is
1546 better to not use notify_notification_update to avoid
1547 overwriting the current notification message */
1548 notify_notification_update (notification,
1549 header, escaped, NULL);
1553 /* if the notification server supports x-canonical-append,
1554 the hint will be added, so that the message from the
1555 just created notification will be automatically appended
1556 to an existing notification with the same title.
1557 In this way the previous message will not be lost: the new
1558 message will appear below it, in the same notification */
1559 const gchar *category = empathy_chat_is_room (chat)
1560 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1561 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1562 notification = notify_notification_new (header, escaped, NULL);
1564 if (self->priv->notification == NULL)
1565 self->priv->notification = notification;
1567 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1569 tp_g_signal_connect_object (notification, "closed",
1570 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1572 if (has_x_canonical_append)
1574 /* We have to set a not empty string to keep libnotify happy */
1575 notify_notification_set_hint_string (notification,
1576 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1579 notify_notification_set_hint (notification,
1580 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1583 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1584 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1588 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1589 g_object_unref (pixbuf);
1592 notify_notification_show (notification, NULL);
1598 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1602 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1604 g_object_get (self->priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1610 chat_window_new_message_cb (EmpathyChat *chat,
1611 EmpathyMessage *message,
1613 gboolean should_highlight,
1614 EmpathyChatWindow *self)
1617 gboolean needs_urgency;
1618 EmpathyContact *sender;
1620 has_focus = empathy_chat_window_has_focus (self);
1622 /* - if we're the sender, we play the sound if it's specified in the
1623 * preferences and we're not away.
1624 * - if we receive a message, we play the sound if it's specified in the
1625 * preferences and the window does not have focus on the chat receiving
1629 sender = empathy_message_get_sender (message);
1631 if (empathy_contact_is_user (sender))
1633 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self->priv->dialog),
1634 EMPATHY_SOUND_MESSAGE_OUTGOING);
1638 if (has_focus && self->priv->current_chat == chat)
1640 /* window and tab are focused so consider the message to be read */
1642 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1643 empathy_chat_messages_read (chat);
1647 /* Update the chat tab if this is the first unread message */
1648 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1650 chat_window_update_chat_tab (chat);
1653 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1654 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1655 * an unamed MUC (msn-like).
1656 * In case of a MUC, we set urgency if either:
1657 * a) the chatroom's always_urgent property is TRUE
1658 * b) the message contains our alias
1660 if (empathy_chat_is_room (chat))
1664 EmpathyChatroom *chatroom;
1666 account = empathy_chat_get_account (chat);
1667 room = empathy_chat_get_id (chat);
1669 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1672 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1673 needs_urgency = TRUE;
1675 needs_urgency = should_highlight;
1679 needs_urgency = TRUE;
1685 chat_window_set_urgency_hint (self, TRUE);
1687 /* Pending messages have already been displayed and notified in the
1688 * approver, so we don't display a notification and play a sound
1692 empathy_sound_manager_play (self->priv->sound_mgr,
1693 GTK_WIDGET (self->priv->dialog), EMPATHY_SOUND_MESSAGE_INCOMING);
1695 chat_window_show_or_update_notification (self, message, chat);
1699 /* update the number of unread messages and the window icon */
1700 chat_window_title_update (self);
1701 chat_window_icon_update (self, TRUE);
1705 chat_window_command_part (EmpathyChat *chat,
1708 EmpathyChat *chat_to_be_parted;
1709 EmpathyTpChat *tp_chat = NULL;
1711 if (strv[1] == NULL)
1713 /* No chatroom ID specified */
1714 tp_chat = empathy_chat_get_tp_chat (chat);
1717 empathy_tp_chat_leave (tp_chat, "");
1722 chat_to_be_parted = empathy_chat_window_find_chat (
1723 empathy_chat_get_account (chat), strv[1], FALSE);
1725 if (chat_to_be_parted != NULL)
1727 /* Found a chatroom matching the specified ID */
1728 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1731 empathy_tp_chat_leave (tp_chat, strv[2]);
1737 /* Going by the syntax of PART command:
1739 * /PART [<chatroom-ID>] [<reason>]
1741 * Chatroom-ID is not a must to specify a reason.
1742 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1743 * MUC then the current chatroom should be parted and srtv[1] should
1744 * be treated as part of the optional part-message. */
1745 message = g_strconcat (strv[1], " ", strv[2], NULL);
1746 tp_chat = empathy_chat_get_tp_chat (chat);
1749 empathy_tp_chat_leave (tp_chat, message);
1755 static GtkNotebook *
1756 notebook_create_window_cb (GtkNotebook *source,
1762 EmpathyChatWindow *window, *new_window;
1765 chat = EMPATHY_CHAT (page);
1766 window = chat_window_find_chat (chat);
1768 new_window = empathy_chat_window_new ();
1770 DEBUG ("Detach hook called");
1772 empathy_chat_window_move_chat (window, new_window, chat);
1774 gtk_widget_show (new_window->priv->dialog);
1775 gtk_window_move (GTK_WINDOW (new_window->priv->dialog), x, y);
1781 chat_window_page_switched_cb (GtkNotebook *notebook,
1784 EmpathyChatWindow *self)
1786 EmpathyChat *chat = EMPATHY_CHAT (child);
1788 DEBUG ("Page switched");
1790 if (self->priv->page_added)
1792 self->priv->page_added = FALSE;
1793 empathy_chat_scroll_down (chat);
1795 else if (self->priv->current_chat == chat)
1800 self->priv->current_chat = chat;
1801 empathy_chat_messages_read (chat);
1803 chat_window_update_chat_tab (chat);
1807 chat_window_page_added_cb (GtkNotebook *notebook,
1810 EmpathyChatWindow *self)
1814 /* If we just received DND to the same window, we don't want
1815 * to do anything here like removing the tab and then readding
1816 * it, so we return here and in "page-added".
1818 if (self->priv->dnd_same_window)
1820 DEBUG ("Page added (back to the same window)");
1821 self->priv->dnd_same_window = FALSE;
1825 DEBUG ("Page added");
1827 /* Get chat object */
1828 chat = EMPATHY_CHAT (child);
1830 /* Connect chat signals for this window */
1831 g_signal_connect (chat, "composing",
1832 G_CALLBACK (chat_window_composing_cb), self);
1833 g_signal_connect (chat, "new-message",
1834 G_CALLBACK (chat_window_new_message_cb), self);
1835 g_signal_connect (chat, "part-command-entered",
1836 G_CALLBACK (chat_window_command_part), NULL);
1837 g_signal_connect (chat, "notify::tp-chat",
1838 G_CALLBACK (chat_window_update_chat_tab), self);
1840 /* Set flag so we know to perform some special operations on
1841 * switch page due to the new page being added.
1843 self->priv->page_added = TRUE;
1845 /* Get list of chats up to date */
1846 self->priv->chats = g_list_append (self->priv->chats, chat);
1848 chat_window_update_chat_tab (chat);
1852 chat_window_page_removed_cb (GtkNotebook *notebook,
1855 EmpathyChatWindow *self)
1859 /* If we just received DND to the same window, we don't want
1860 * to do anything here like removing the tab and then readding
1861 * it, so we return here and in "page-added".
1863 if (self->priv->dnd_same_window)
1865 DEBUG ("Page removed (and will be readded to same window)");
1869 DEBUG ("Page removed");
1871 /* Get chat object */
1872 chat = EMPATHY_CHAT (child);
1874 /* Disconnect all signal handlers for this chat and this window */
1875 g_signal_handlers_disconnect_by_func (chat,
1876 G_CALLBACK (chat_window_composing_cb), self);
1877 g_signal_handlers_disconnect_by_func (chat,
1878 G_CALLBACK (chat_window_new_message_cb), self);
1879 g_signal_handlers_disconnect_by_func (chat,
1880 G_CALLBACK (chat_window_update_chat_tab), self);
1882 /* Keep list of chats up to date */
1883 self->priv->chats = g_list_remove (self->priv->chats, chat);
1884 empathy_chat_messages_read (chat);
1886 if (self->priv->chats == NULL)
1888 g_object_unref (self);
1892 chat_window_update (self, TRUE);
1897 chat_window_focus_in_event_cb (GtkWidget *widget,
1899 EmpathyChatWindow *self)
1901 empathy_chat_messages_read (self->priv->current_chat);
1903 chat_window_set_urgency_hint (self, FALSE);
1905 /* Update the title, since we now mark all unread messages as read. */
1906 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1912 chat_window_drag_drop (GtkWidget *widget,
1913 GdkDragContext *context,
1917 EmpathyChatWindow *self)
1921 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1922 if (target == GDK_NONE)
1923 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1925 if (target != GDK_NONE)
1927 gtk_drag_get_data (widget, context, target, time_);
1935 chat_window_drag_motion (GtkWidget *widget,
1936 GdkDragContext *context,
1940 EmpathyChatWindow *self)
1944 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1946 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 contact = empathy_chat_get_remote_contact (self->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))
1965 gdk_drag_status (context, 0, time_);
1969 if (!(empathy_contact_get_capabilities (contact)
1970 & EMPATHY_CAPABILITIES_FT))
1972 gdk_drag_status (context, 0, time_);
1976 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1980 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1981 if (target != GDK_NONE)
1983 /* This is a drag of a contact from a contact list. Set to COPY.
1984 FIXME: If this drag is to a MUC window, it invites the user.
1985 Otherwise, it opens a chat. Should we use a different drag
1986 type for invites? Should we allow ASK?
1988 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1996 drag_data_received_individual_id (EmpathyChatWindow *self,
1998 GdkDragContext *context,
2001 GtkSelectionData *selection,
2006 EmpathyIndividualManager *manager = NULL;
2007 FolksIndividual *individual;
2008 EmpathyTpChat *chat;
2009 TpContact *tp_contact;
2011 EmpathyContact *contact;
2013 id = (const gchar *) gtk_selection_data_get_data (selection);
2015 DEBUG ("DND invididual %s", id);
2017 if (self->priv->current_chat == NULL)
2020 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2024 if (!empathy_tp_chat_can_add_contact (chat))
2026 DEBUG ("Can't invite contact to %s",
2027 tp_proxy_get_object_path (chat));
2031 manager = empathy_individual_manager_dup_singleton ();
2033 individual = empathy_individual_manager_lookup_member (manager, id);
2034 if (individual == NULL)
2036 DEBUG ("Failed to find individual %s", id);
2040 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2041 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2042 if (tp_contact == NULL)
2044 DEBUG ("Can't find a TpContact on connection %s for %s",
2045 tp_proxy_get_object_path (conn), id);
2049 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2050 tp_channel_get_identifier ((TpChannel *) chat));
2052 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2053 empathy_tp_chat_add (chat, contact, NULL);
2054 g_object_unref (contact);
2057 gtk_drag_finish (context, TRUE, FALSE, time_);
2058 tp_clear_object (&manager);
2062 chat_window_drag_data_received (GtkWidget *widget,
2063 GdkDragContext *context,
2066 GtkSelectionData *selection,
2069 EmpathyChatWindow *self)
2071 if (info == DND_DRAG_TYPE_CONTACT_ID)
2073 EmpathyChat *chat = NULL;
2074 EmpathyChatWindow *old_window;
2075 TpAccount *account = NULL;
2076 EmpathyClientFactory *factory;
2079 const gchar *account_id;
2080 const gchar *contact_id;
2082 id = (const gchar*) gtk_selection_data_get_data (selection);
2084 factory = empathy_client_factory_dup ();
2086 DEBUG ("DND contact from roster with id:'%s'", id);
2088 strv = g_strsplit (id, ":", 2);
2089 if (g_strv_length (strv) == 2)
2091 account_id = strv[0];
2092 contact_id = strv[1];
2094 account = tp_simple_client_factory_ensure_account (
2095 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2097 g_object_unref (factory);
2098 if (account != NULL)
2099 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2102 if (account == NULL)
2105 gtk_drag_finish (context, FALSE, FALSE, time_);
2111 empathy_chat_with_contact_id (account, contact_id,
2112 empathy_get_current_action_time (), NULL, NULL);
2120 old_window = chat_window_find_chat (chat);
2123 if (old_window == self)
2125 gtk_drag_finish (context, TRUE, FALSE, time_);
2129 empathy_chat_window_move_chat (old_window, self, chat);
2133 empathy_chat_window_add_chat (self, chat);
2136 /* Added to take care of any outstanding chat events */
2137 empathy_chat_window_present_chat (chat,
2138 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2140 /* We should return TRUE to remove the data when doing
2141 * GDK_ACTION_MOVE, but we don't here otherwise it has
2142 * weird consequences, and we handle that internally
2143 * anyway with add_chat () and remove_chat ().
2145 gtk_drag_finish (context, TRUE, FALSE, time_);
2147 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2149 drag_data_received_individual_id (self, widget, context, x, y,
2150 selection, info, time_);
2152 else if (info == DND_DRAG_TYPE_URI_LIST)
2154 EmpathyContact *contact;
2157 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2159 /* contact is NULL when current_chat is a multi-user chat.
2160 * We don't do file transfers to MUCs, so just cancel the drag.
2162 if (contact == NULL)
2164 gtk_drag_finish (context, TRUE, FALSE, time_);
2168 data = (const gchar *) gtk_selection_data_get_data (selection);
2169 empathy_send_file_from_uri_list (contact, data);
2171 gtk_drag_finish (context, TRUE, FALSE, time_);
2173 else if (info == DND_DRAG_TYPE_TAB)
2176 EmpathyChatWindow *old_window = NULL;
2180 chat = (void *) gtk_selection_data_get_data (selection);
2181 old_window = chat_window_find_chat (*chat);
2185 self->priv->dnd_same_window = (old_window == self);
2187 DEBUG ("DND tab (within same window: %s)",
2188 self->priv->dnd_same_window ? "Yes" : "No");
2193 DEBUG ("DND from unknown source");
2194 gtk_drag_finish (context, FALSE, FALSE, time_);
2199 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2200 guint num_chats_in_manager,
2201 EmpathyChatWindow *self)
2203 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2204 num_chats_in_manager > 0);
2208 chat_window_finalize (GObject *object)
2210 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2212 DEBUG ("Finalized: %p", object);
2214 g_object_unref (self->priv->ui_manager);
2215 g_object_unref (self->priv->chatroom_manager);
2216 g_object_unref (self->priv->notify_mgr);
2217 g_object_unref (self->priv->gsettings_chat);
2218 g_object_unref (self->priv->gsettings_notif);
2219 g_object_unref (self->priv->gsettings_ui);
2220 g_object_unref (self->priv->sound_mgr);
2222 if (self->priv->notification != NULL)
2224 notify_notification_close (self->priv->notification, NULL);
2225 self->priv->notification = NULL;
2228 if (self->priv->contact_targets)
2229 gtk_target_list_unref (self->priv->contact_targets);
2231 if (self->priv->file_targets)
2232 gtk_target_list_unref (self->priv->file_targets);
2234 if (self->priv->chat_manager)
2236 g_signal_handler_disconnect (self->priv->chat_manager,
2237 self->priv->chat_manager_chats_changed_id);
2238 g_object_unref (self->priv->chat_manager);
2239 self->priv->chat_manager = NULL;
2242 chat_windows = g_list_remove (chat_windows, self);
2243 gtk_widget_destroy (self->priv->dialog);
2245 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2249 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2251 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2253 object_class->finalize = chat_window_finalize;
2255 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2259 empathy_chat_window_init (EmpathyChatWindow *self)
2262 GtkAccelGroup *accel_group;
2267 GtkWidget *chat_vbox;
2269 EmpathySmileyManager *smiley_manager;
2271 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2272 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2274 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2275 gui = empathy_builder_get_file (filename,
2276 "chat_window", &self->priv->dialog,
2277 "chat_vbox", &chat_vbox,
2278 "ui_manager", &self->priv->ui_manager,
2279 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2280 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2281 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2282 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2283 "menu_edit_cut", &self->priv->menu_edit_cut,
2284 "menu_edit_copy", &self->priv->menu_edit_copy,
2285 "menu_edit_paste", &self->priv->menu_edit_paste,
2286 "menu_edit_find", &self->priv->menu_edit_find,
2287 "menu_tabs_next", &self->priv->menu_tabs_next,
2288 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2289 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2290 "menu_tabs_left", &self->priv->menu_tabs_left,
2291 "menu_tabs_right", &self->priv->menu_tabs_right,
2292 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2296 empathy_builder_connect (gui, self,
2297 "menu_conv", "activate", chat_window_conv_activate_cb,
2298 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2299 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2300 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2301 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2302 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2303 "menu_conv_close", "activate", chat_window_close_activate_cb,
2304 "menu_edit", "activate", chat_window_edit_activate_cb,
2305 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2306 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2307 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2308 "menu_edit_find", "activate", chat_window_find_activate_cb,
2309 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2310 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2311 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2312 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2313 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2314 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2315 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2316 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2319 g_object_ref (self->priv->ui_manager);
2320 g_object_unref (gui);
2322 empathy_set_css_provider (GTK_WIDGET (self->priv->dialog));
2324 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2325 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2326 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2327 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2329 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2331 self->priv->notebook = gtk_notebook_new ();
2333 g_signal_connect (self->priv->notebook, "create-window",
2334 G_CALLBACK (notebook_create_window_cb), self);
2336 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2337 "EmpathyChatWindow");
2338 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2339 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2340 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2341 gtk_widget_show (self->priv->notebook);
2344 accel_group = gtk_accel_group_new ();
2345 gtk_window_add_accel_group (GTK_WINDOW (self->priv->dialog), accel_group);
2347 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2349 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2352 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2356 g_object_unref (accel_group);
2358 /* Set up drag target lists */
2359 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2360 G_N_ELEMENTS (drag_types_dest_contact));
2362 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2363 G_N_ELEMENTS (drag_types_dest_file));
2365 /* Set up smiley menu */
2366 smiley_manager = empathy_smiley_manager_dup_singleton ();
2367 submenu = empathy_smiley_menu_new (smiley_manager,
2368 chat_window_insert_smiley_activate_cb, self);
2370 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2371 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2372 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2373 g_object_unref (smiley_manager);
2375 /* Set up signals we can't do with ui file since we may need to
2376 * block/unblock them at some later stage.
2379 g_signal_connect (self->priv->dialog, "delete_event",
2380 G_CALLBACK (chat_window_delete_event_cb), self);
2381 g_signal_connect (self->priv->dialog, "focus_in_event",
2382 G_CALLBACK (chat_window_focus_in_event_cb), self);
2383 g_signal_connect_after (self->priv->notebook, "switch_page",
2384 G_CALLBACK (chat_window_page_switched_cb), self);
2385 g_signal_connect (self->priv->notebook, "page_added",
2386 G_CALLBACK (chat_window_page_added_cb), self);
2387 g_signal_connect (self->priv->notebook, "page_removed",
2388 G_CALLBACK (chat_window_page_removed_cb), self);
2390 /* Set up drag and drop */
2391 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2392 GTK_DEST_DEFAULT_HIGHLIGHT,
2394 G_N_ELEMENTS (drag_types_dest),
2395 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2397 /* connect_after to allow GtkNotebook's built-in tab switching */
2398 g_signal_connect_after (self->priv->notebook, "drag-motion",
2399 G_CALLBACK (chat_window_drag_motion), self);
2400 g_signal_connect (self->priv->notebook, "drag-data-received",
2401 G_CALLBACK (chat_window_drag_data_received), self);
2402 g_signal_connect (self->priv->notebook, "drag-drop",
2403 G_CALLBACK (chat_window_drag_drop), self);
2405 chat_windows = g_list_prepend (chat_windows, self);
2407 /* Set up private details */
2408 self->priv->chats = NULL;
2409 self->priv->current_chat = NULL;
2410 self->priv->notification = NULL;
2412 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2414 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2415 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2416 self->priv->chat_manager, "closed-chats-changed",
2417 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2419 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2420 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2423 /* Returns the window to open a new tab in if there is a suitable window,
2424 * otherwise, returns NULL indicating that a new window should be added.
2426 static EmpathyChatWindow *
2427 empathy_chat_window_get_default (gboolean room)
2429 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2431 gboolean separate_windows = TRUE;
2433 separate_windows = g_settings_get_boolean (gsettings,
2434 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2436 g_object_unref (gsettings);
2438 if (separate_windows)
2439 /* Always create a new window */
2442 for (l = chat_windows; l; l = l->next)
2444 EmpathyChatWindow *chat_window;
2445 guint nb_rooms, nb_private;
2447 chat_window = l->data;
2449 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2451 /* Skip the window if there aren't any rooms in it */
2452 if (room && nb_rooms == 0)
2455 /* Skip the window if there aren't any 1-1 chats in it */
2456 if (!room && nb_private == 0)
2466 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2470 GtkWidget *popup_label;
2472 GValue value = { 0, };
2474 g_return_if_fail (self != NULL);
2475 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2477 /* Reference the chat object */
2478 g_object_ref (chat);
2480 /* If this window has just been created, position it */
2481 if (self->priv->chats == NULL)
2483 const gchar *name = "chat-window";
2484 gboolean separate_windows;
2486 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2487 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2489 if (empathy_chat_is_room (chat))
2490 name = "room-window";
2492 if (separate_windows)
2496 /* Save current position of the window */
2497 gtk_window_get_position (GTK_WINDOW (self->priv->dialog), &x, &y);
2499 /* First bind to the 'generic' name. So new window for which we didn't
2500 * save a geometry yet will have the geometry of the last saved
2501 * window (bgo #601191). */
2502 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2504 /* Restore previous position of the window so the newly created window
2505 * won't be in the same position as the latest saved window and so
2506 * completely hide it. */
2507 gtk_window_move (GTK_WINDOW (self->priv->dialog), x, y);
2509 /* Then bind it to the name of the contact/room so we'll save the
2510 * geometry specific to this window */
2511 name = empathy_chat_get_id (chat);
2514 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2517 child = GTK_WIDGET (chat);
2518 label = chat_window_create_label (self, chat, TRUE);
2519 popup_label = chat_window_create_label (self, chat, FALSE);
2520 gtk_widget_show (child);
2522 g_signal_connect (chat, "notify::name",
2523 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2524 g_signal_connect (chat, "notify::subject",
2525 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2526 g_signal_connect (chat, "notify::remote-contact",
2527 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2528 g_signal_connect (chat, "notify::sms-channel",
2529 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2530 g_signal_connect (chat, "notify::n-messages-sending",
2531 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2532 g_signal_connect (chat, "notify::nb-unread-messages",
2533 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2534 chat_window_chat_notify_cb (chat);
2536 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2538 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2539 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2540 g_value_init (&value, G_TYPE_BOOLEAN);
2541 g_value_set_boolean (&value, TRUE);
2542 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2543 child, "tab-expand" , &value);
2544 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2545 child, "tab-fill" , &value);
2546 g_value_unset (&value);
2548 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2552 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2556 EmpathyContact *remote_contact;
2557 EmpathyChatManager *chat_manager;
2559 g_return_if_fail (self != NULL);
2560 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2562 g_signal_handlers_disconnect_by_func (chat,
2563 chat_window_chat_notify_cb, NULL);
2565 remote_contact = g_object_get_data (G_OBJECT (chat),
2566 "chat-window-remote-contact");
2570 g_signal_handlers_disconnect_by_func (remote_contact,
2571 chat_window_update_chat_tab, chat);
2574 chat_manager = empathy_chat_manager_dup_singleton ();
2575 empathy_chat_manager_closed_chat (chat_manager, chat);
2576 g_object_unref (chat_manager);
2578 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2580 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2582 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2584 g_object_unref (chat);
2588 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2589 EmpathyChatWindow *new_window,
2594 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2595 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2596 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2598 widget = GTK_WIDGET (chat);
2600 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2601 G_OBJECT (widget)->ref_count);
2603 /* We reference here to make sure we don't loose the widget
2604 * and the EmpathyChat object during the move.
2606 g_object_ref (chat);
2607 g_object_ref (widget);
2609 empathy_chat_window_remove_chat (old_window, chat);
2610 empathy_chat_window_add_chat (new_window, chat);
2612 g_object_unref (widget);
2613 g_object_unref (chat);
2617 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2622 g_return_if_fail (self != NULL);
2623 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2625 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2628 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2633 empathy_chat_window_find_chat (TpAccount *account,
2635 gboolean sms_channel)
2639 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2641 for (l = chat_windows; l; l = l->next)
2643 EmpathyChatWindow *window = l->data;
2646 for (ll = window->priv->chats; ll; ll = ll->next)
2652 if (account == empathy_chat_get_account (chat) &&
2653 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2654 sms_channel == empathy_chat_is_sms_channel (chat))
2663 empathy_chat_window_present_chat (EmpathyChat *chat,
2666 EmpathyChatWindow *self;
2667 guint32 x_timestamp;
2669 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2671 self = chat_window_find_chat (chat);
2673 /* If the chat has no window, create one */
2676 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2679 self = empathy_chat_window_new ();
2681 /* we want to display the newly created window even if we
2682 * don't present it */
2683 gtk_widget_show (self->priv->dialog);
2686 empathy_chat_window_add_chat (self, chat);
2689 /* Don't force the window to show itself when it wasn't
2690 * an action by the user
2692 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2695 if (x_timestamp != GDK_CURRENT_TIME)
2697 /* Don't present or switch tab if the action was earlier than the
2698 * last actions X time, accounting for overflow and the first ever
2701 if (self->priv->x_user_action_time != 0
2702 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2705 self->priv->x_user_action_time = x_timestamp;
2708 empathy_chat_window_switch_to_chat (self, chat);
2710 /* Don't use empathy_window_present_with_time () which would move the window
2711 * to our current desktop but move to the window's desktop instead. This is
2712 * more coherent with Shell's 'app is ready' notication which moves the view
2713 * to the app desktop rather than moving the app itself. */
2714 empathy_move_to_window_desktop (GTK_WINDOW (self->priv->dialog), x_timestamp);
2716 gtk_widget_grab_focus (chat->input_text_view);
2720 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2725 guint _nb_rooms = 0, _nb_private = 0;
2727 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2729 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2735 if (nb_rooms != NULL)
2736 *nb_rooms = _nb_rooms;
2737 if (nb_private != NULL)
2738 *nb_private = _nb_private;