1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2003-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Richard Hult <richard@imendio.com>
23 * Martyn Russell <martyn@imendio.com>
24 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
25 * Xavier Claessens <xclaesse@gmail.com>
26 * RĂ´mulo Fernandes Machado <romulo@castorgroup.net>
34 #include <gdk/gdkkeysyms.h>
36 #include <glib/gi18n.h>
37 #include <libnotify/notification.h>
39 #include <telepathy-glib/telepathy-glib.h>
41 #include <libempathy/empathy-client-factory.h>
42 #include <libempathy/empathy-contact.h>
43 #include <libempathy/empathy-message.h>
44 #include <libempathy/empathy-chatroom-manager.h>
45 #include <libempathy/empathy-gsettings.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-tp-contact-factory.h>
48 #include <libempathy/empathy-contact-list.h>
49 #include <libempathy/empathy-request-util.h>
51 #include <libempathy-gtk/empathy-images.h>
52 #include <libempathy-gtk/empathy-contact-dialogs.h>
53 #include <libempathy-gtk/empathy-log-window.h>
54 #include <libempathy-gtk/empathy-geometry.h>
55 #include <libempathy-gtk/empathy-smiley-manager.h>
56 #include <libempathy-gtk/empathy-sound-manager.h>
57 #include <libempathy-gtk/empathy-ui-utils.h>
58 #include <libempathy-gtk/empathy-notify-manager.h>
60 #include "empathy-chat-manager.h"
61 #include "empathy-chat-window.h"
62 #include "empathy-about-dialog.h"
63 #include "empathy-invite-participant-dialog.h"
64 #include "gedit-close-button.h"
66 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
67 #include <libempathy/empathy-debug.h>
69 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
71 #define X_EARLIER_OR_EQL(t1, t2) \
72 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
73 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
76 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
78 EmpathyChat *current_chat;
81 gboolean dnd_same_window;
82 EmpathyChatroomManager *chatroom_manager;
83 EmpathyNotifyManager *notify_mgr;
86 NotifyNotification *notification;
88 GtkTargetList *contact_targets;
89 GtkTargetList *file_targets;
91 EmpathyChatManager *chat_manager;
92 gulong chat_manager_chats_changed_id;
95 GtkUIManager *ui_manager;
96 GtkAction *menu_conv_insert_smiley;
97 GtkAction *menu_conv_favorite;
98 GtkAction *menu_conv_always_urgent;
99 GtkAction *menu_conv_toggle_contacts;
101 GtkAction *menu_edit_cut;
102 GtkAction *menu_edit_copy;
103 GtkAction *menu_edit_paste;
104 GtkAction *menu_edit_find;
106 GtkAction *menu_tabs_next;
107 GtkAction *menu_tabs_prev;
108 GtkAction *menu_tabs_undo_close_tab;
109 GtkAction *menu_tabs_left;
110 GtkAction *menu_tabs_right;
111 GtkAction *menu_tabs_detach;
113 /* Last user action time we acted upon to show a tab */
114 guint32 x_user_action_time;
116 GSettings *gsettings_chat;
117 GSettings *gsettings_notif;
118 GSettings *gsettings_ui;
120 EmpathySoundManager *sound_mgr;
121 } EmpathyChatWindowPriv;
123 static GList *chat_windows = NULL;
125 static const guint tab_accel_keys[] = {
126 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
127 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
131 DND_DRAG_TYPE_CONTACT_ID,
132 DND_DRAG_TYPE_URI_LIST,
136 static const GtkTargetEntry drag_types_dest[] = {
137 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
138 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
139 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
140 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] = {
144 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
147 static const GtkTargetEntry drag_types_dest_file[] = {
148 /* must be first to be prioritized, in order to receive the
149 * note's file path from Tomboy instead of an URI */
150 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
151 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
154 static void chat_window_update (EmpathyChatWindow *window,
155 gboolean update_contact_menu);
157 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
160 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
163 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
164 EmpathyChatWindow *new_window,
167 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
171 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
174 chat_window_accel_cb (GtkAccelGroup *accelgroup,
178 EmpathyChatWindow *window)
180 EmpathyChatWindowPriv *priv;
184 priv = GET_PRIV (window);
186 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
187 if (tab_accel_keys[i] == key) {
194 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
198 static EmpathyChatWindow *
199 chat_window_find_chat (EmpathyChat *chat)
201 EmpathyChatWindowPriv *priv;
204 for (l = chat_windows; l; l = l->next) {
205 priv = GET_PRIV (l->data);
206 ll = g_list_find (priv->chats, chat);
216 remove_all_chats (EmpathyChatWindow *window)
218 EmpathyChatWindowPriv *priv;
220 priv = GET_PRIV (window);
221 g_object_ref (window);
223 while (priv->chats) {
224 empathy_chat_window_remove_chat (window, priv->chats->data);
227 g_object_unref (window);
231 confirm_close_response_cb (GtkWidget *dialog,
233 EmpathyChatWindow *window)
237 chat = g_object_get_data (G_OBJECT (dialog), "chat");
239 gtk_widget_destroy (dialog);
241 if (response != GTK_RESPONSE_ACCEPT)
245 empathy_chat_window_remove_chat (window, chat);
247 remove_all_chats (window);
252 confirm_close (EmpathyChatWindow *window,
253 gboolean close_window,
257 EmpathyChatWindowPriv *priv;
259 gchar *primary, *secondary;
261 g_return_if_fail (n_rooms > 0);
264 g_return_if_fail (chat == NULL);
266 g_return_if_fail (chat != NULL);
269 priv = GET_PRIV (window);
272 primary = g_strdup (_("Close this window?"));
275 gchar *chat_name = empathy_chat_dup_name (chat);
276 secondary = g_strdup_printf (
277 _("Closing this window will leave %s. You will "
278 "not receive any further messages until you "
283 secondary = g_strdup_printf (
284 /* Note to translators: the number of chats will
285 * always be at least 2.
288 "Closing this window will leave a chat room. You will "
289 "not receive any further messages until you rejoin it.",
290 "Closing this window will leave %u chat rooms. You will "
291 "not receive any further messages until you rejoin them.",
296 gchar *chat_name = empathy_chat_dup_name (chat);
297 primary = g_strdup_printf (_("Leave %s?"), chat_name);
298 secondary = g_strdup (_("You will not receive any further messages from this chat "
299 "room until you rejoin it."));
303 dialog = gtk_message_dialog_new (
304 GTK_WINDOW (priv->dialog),
305 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
310 gtk_window_set_title (GTK_WINDOW (dialog), "");
311 g_object_set (dialog, "secondary-text", secondary, NULL);
316 gtk_dialog_add_button (GTK_DIALOG (dialog),
317 close_window ? _("Close window") : _("Leave chat room"),
318 GTK_RESPONSE_ACCEPT);
319 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
320 GTK_RESPONSE_ACCEPT);
323 g_object_set_data (G_OBJECT (dialog), "chat", chat);
326 g_signal_connect (dialog, "response",
327 G_CALLBACK (confirm_close_response_cb), window);
329 gtk_window_present (GTK_WINDOW (dialog));
333 maybe_close_chat (EmpathyChatWindow *window,
336 g_return_if_fail (chat != NULL);
338 if (empathy_chat_is_room (chat)) {
339 confirm_close (window, FALSE, 1, chat);
341 empathy_chat_window_remove_chat (window, chat);
346 chat_window_close_clicked_cb (GtkAction *action,
349 EmpathyChatWindow *window;
351 window = chat_window_find_chat (chat);
352 maybe_close_chat (window, chat);
356 chat_tab_style_updated_cb (GtkWidget *hbox,
360 int char_width, h, w;
361 PangoContext *context;
362 const PangoFontDescription *font_desc;
363 PangoFontMetrics *metrics;
365 button = g_object_get_data (G_OBJECT (user_data),
366 "chat-window-tab-close-button");
367 context = gtk_widget_get_pango_context (hbox);
369 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
370 GTK_STATE_FLAG_NORMAL);
372 metrics = pango_context_get_metrics (context, font_desc,
373 pango_context_get_language (context));
374 char_width = pango_font_metrics_get_approximate_char_width (metrics);
375 pango_font_metrics_unref (metrics);
377 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
378 GTK_ICON_SIZE_MENU, &w, &h);
380 /* Request at least about 12 chars width plus at least space for the status
381 * image and the close button */
382 gtk_widget_set_size_request (hbox,
383 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
385 gtk_widget_set_size_request (button, w, h);
389 chat_window_create_label (EmpathyChatWindow *window,
391 gboolean is_tab_label)
394 GtkWidget *name_label;
395 GtkWidget *status_image;
396 GtkWidget *event_box;
397 GtkWidget *event_box_hbox;
398 PangoAttrList *attr_list;
399 PangoAttribute *attr;
401 /* The spacing between the button and the label. */
402 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
404 event_box = gtk_event_box_new ();
405 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
407 name_label = gtk_label_new (NULL);
409 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
411 attr_list = pango_attr_list_new ();
412 attr = pango_attr_scale_new (1/1.2);
413 attr->start_index = 0;
414 attr->end_index = -1;
415 pango_attr_list_insert (attr_list, attr);
416 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
417 pango_attr_list_unref (attr_list);
419 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
420 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
421 g_object_set_data (G_OBJECT (chat),
422 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
425 status_image = gtk_image_new ();
427 /* Spacing between the icon and label. */
428 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
430 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
431 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
433 g_object_set_data (G_OBJECT (chat),
434 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
436 g_object_set_data (G_OBJECT (chat),
437 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
440 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
441 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
444 GtkWidget *close_button;
445 GtkWidget *sending_spinner;
447 sending_spinner = gtk_spinner_new ();
449 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
451 g_object_set_data (G_OBJECT (chat),
452 "chat-window-tab-sending-spinner",
455 close_button = gedit_close_button_new ();
456 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
458 /* We don't want focus/keynav for the button to avoid clutter, and
459 * Ctrl-W works anyway.
461 gtk_widget_set_can_focus (close_button, FALSE);
462 gtk_widget_set_can_default (close_button, FALSE);
464 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
466 g_signal_connect (close_button,
468 G_CALLBACK (chat_window_close_clicked_cb),
471 /* React to theme changes and also setup the size correctly. */
472 g_signal_connect (hbox,
474 G_CALLBACK (chat_tab_style_updated_cb),
478 gtk_widget_show_all (hbox);
484 _submenu_notify_visible_changed_cb (GObject *object,
488 g_signal_handlers_disconnect_by_func (object,
489 _submenu_notify_visible_changed_cb,
491 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
495 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
500 gboolean wrap_around;
501 gboolean is_connected;
504 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
505 first_page = (page_num == 0);
506 last_page = (page_num == (num_pages - 1));
507 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
509 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
511 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
513 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
515 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
516 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
517 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
518 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
522 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
523 EmpathyChatWindow *self)
525 EmpathyTpChat *tp_chat;
526 TpConnection *connection;
528 gboolean sensitive = FALSE;
530 g_return_if_fail (priv->current_chat != NULL);
532 action = gtk_ui_manager_get_action (priv->ui_manager,
533 "/chats_menubar/menu_conv/menu_conv_invite_participant");
534 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
536 if (tp_chat != NULL) {
537 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
539 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
540 (tp_connection_get_status (connection, NULL) ==
541 TP_CONNECTION_STATUS_CONNECTED);
544 gtk_action_set_sensitive (action, sensitive);
548 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
549 EmpathyChatWindow *window)
551 GtkWidget *menu, *submenu, *orig_submenu;
553 menu = gtk_ui_manager_get_widget (priv->ui_manager,
554 "/chats_menubar/menu_contact");
555 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
557 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
558 submenu = empathy_chat_get_contact_menu (priv->current_chat);
560 if (submenu != NULL) {
561 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
562 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
564 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
565 gtk_widget_show (menu);
566 gtk_widget_set_sensitive (menu, TRUE);
568 gtk_widget_set_sensitive (menu, FALSE);
571 tp_g_signal_connect_object (orig_submenu,
573 (GCallback)_submenu_notify_visible_changed_cb,
579 get_all_unread_messages (EmpathyChatWindowPriv *priv)
584 for (l = priv->chats; l != NULL; l = g_list_next (l))
585 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
591 get_window_title_name (EmpathyChatWindowPriv *priv)
593 gchar *active_name, *ret;
595 guint current_unread_msgs;
597 nb_chats = g_list_length (priv->chats);
598 g_assert (nb_chats > 0);
600 active_name = empathy_chat_dup_name (priv->current_chat);
602 current_unread_msgs = empathy_chat_get_nb_unread_messages (
607 if (current_unread_msgs == 0)
608 ret = g_strdup (active_name);
610 ret = g_strdup_printf (ngettext (
612 "%s (%d unread)", current_unread_msgs),
613 active_name, current_unread_msgs);
615 guint nb_others = nb_chats - 1;
616 guint all_unread_msgs;
618 all_unread_msgs = get_all_unread_messages (priv);
620 if (all_unread_msgs == 0) {
621 /* no unread message */
622 ret = g_strdup_printf (ngettext (
624 "%s (and %u others)", nb_others),
625 active_name, nb_others);
628 else if (all_unread_msgs == current_unread_msgs) {
629 /* unread messages are in the current tab */
630 ret = g_strdup_printf (ngettext (
632 "%s (%d unread)", current_unread_msgs),
633 active_name, current_unread_msgs);
636 else if (current_unread_msgs == 0) {
637 /* unread messages are in other tabs */
638 ret = g_strdup_printf (ngettext (
639 "%s (%d unread from others)",
640 "%s (%d unread from others)",
642 active_name, all_unread_msgs);
646 /* unread messages are in all the tabs */
647 ret = g_strdup_printf (ngettext (
648 "%s (%d unread from all)",
649 "%s (%d unread from all)",
651 active_name, all_unread_msgs);
655 g_free (active_name);
661 chat_window_title_update (EmpathyChatWindowPriv *priv)
665 name = get_window_title_name (priv);
666 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
671 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
674 EmpathyContact *remote_contact;
675 gboolean avatar_in_icon;
678 n_chats = g_list_length (priv->chats);
680 /* Update window icon */
682 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
683 EMPATHY_IMAGE_MESSAGE);
685 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
686 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
688 if (n_chats == 1 && avatar_in_icon) {
689 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
690 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
691 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
694 g_object_unref (icon);
697 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
703 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
707 GtkWidget *chat_close_button;
710 if (num_pages == 1) {
711 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
712 chat_close_button = g_object_get_data (G_OBJECT (chat),
713 "chat-window-tab-close-button");
714 gtk_widget_hide (chat_close_button);
716 for (i=0; i<num_pages; i++) {
717 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
718 chat_close_button = g_object_get_data (G_OBJECT (chat),
719 "chat-window-tab-close-button");
720 gtk_widget_show (chat_close_button);
726 chat_window_update (EmpathyChatWindow *window,
727 gboolean update_contact_menu)
729 EmpathyChatWindowPriv *priv = GET_PRIV (window);
732 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
734 /* Update Tab menu */
735 chat_window_menu_context_update (priv,
738 chat_window_conversation_menu_update (priv, window);
740 /* If this update is due to a focus-in event, we know the menu will be
741 the same as when we last left it, so no work to do. Besides, if we
742 swap out the menu on a focus-in, we may confuse any external global
744 if (update_contact_menu) {
745 chat_window_contact_menu_update (priv,
749 chat_window_title_update (priv);
751 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
753 chat_window_close_button_update (priv,
758 append_markup_printf (GString *string,
765 va_start (args, format);
767 tmp = g_markup_vprintf_escaped (format, args);
768 g_string_append (string, tmp);
775 chat_window_update_chat_tab_full (EmpathyChat *chat,
776 gboolean update_contact_menu)
778 EmpathyChatWindow *window;
779 EmpathyChatWindowPriv *priv;
780 EmpathyContact *remote_contact;
784 const gchar *subject;
785 const gchar *status = NULL;
789 const gchar *icon_name;
790 GtkWidget *tab_image;
791 GtkWidget *menu_image;
792 GtkWidget *sending_spinner;
795 window = chat_window_find_chat (chat);
799 priv = GET_PRIV (window);
801 /* Get information */
802 name = empathy_chat_dup_name (chat);
803 account = empathy_chat_get_account (chat);
804 subject = empathy_chat_get_subject (chat);
805 remote_contact = empathy_chat_get_remote_contact (chat);
807 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
808 name, tp_proxy_get_object_path (account), subject, remote_contact);
810 /* Update tab image */
811 if (empathy_chat_get_tp_chat (chat) == NULL) {
812 /* No TpChat, we are disconnected */
815 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
816 icon_name = EMPATHY_IMAGE_MESSAGE;
818 else if (remote_contact && empathy_chat_is_composing (chat)) {
819 icon_name = EMPATHY_IMAGE_TYPING;
821 else if (empathy_chat_is_sms_channel (chat)) {
822 icon_name = EMPATHY_IMAGE_SMS;
824 else if (remote_contact) {
825 icon_name = empathy_icon_name_for_contact (remote_contact);
827 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
830 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
831 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
832 if (icon_name != NULL) {
833 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
834 gtk_widget_show (tab_image);
835 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
836 gtk_widget_show (menu_image);
838 gtk_widget_hide (tab_image);
839 gtk_widget_hide (menu_image);
842 /* Update the sending spinner */
843 nb_sending = empathy_chat_get_n_messages_sending (chat);
844 sending_spinner = g_object_get_data (G_OBJECT (chat),
845 "chat-window-tab-sending-spinner");
847 g_object_set (sending_spinner,
848 "active", nb_sending > 0,
849 "visible", nb_sending > 0,
852 /* Update tab tooltip */
853 tooltip = g_string_new (NULL);
855 if (remote_contact) {
856 id = empathy_contact_get_id (remote_contact);
857 status = empathy_contact_get_presence_message (remote_contact);
862 if (empathy_chat_is_sms_channel (chat)) {
863 append_markup_printf (tooltip, "%s ", _("SMS:"));
866 append_markup_printf (tooltip,
867 "<b>%s</b><small> (%s)</small>",
869 tp_account_get_display_name (account));
871 if (nb_sending > 0) {
872 char *tmp = g_strdup_printf (
873 ngettext ("Sending %d message",
874 "Sending %d messages",
878 g_string_append (tooltip, "\n");
879 g_string_append (tooltip, tmp);
881 gtk_widget_set_tooltip_text (sending_spinner, tmp);
885 if (!EMP_STR_EMPTY (status)) {
886 append_markup_printf (tooltip, "\n<i>%s</i>", status);
890 append_markup_printf (tooltip, "\n<b>%s</b> %s",
891 _("Topic:"), subject);
894 if (remote_contact && empathy_chat_is_composing (chat)) {
895 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
898 markup = g_string_free (tooltip, FALSE);
899 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
900 gtk_widget_set_tooltip_markup (widget, markup);
901 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
902 gtk_widget_set_tooltip_markup (widget, markup);
905 /* Update tab and menu label */
906 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
907 gtk_label_set_text (GTK_LABEL (widget), name);
908 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
909 gtk_label_set_text (GTK_LABEL (widget), name);
911 /* Update the window if it's the current chat */
912 if (priv->current_chat == chat) {
913 chat_window_update (window, update_contact_menu);
920 chat_window_update_chat_tab (EmpathyChat *chat)
922 chat_window_update_chat_tab_full (chat, TRUE);
926 chat_window_chat_notify_cb (EmpathyChat *chat)
928 EmpathyChatWindow *window;
929 EmpathyContact *old_remote_contact;
930 EmpathyContact *remote_contact = NULL;
932 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
933 remote_contact = empathy_chat_get_remote_contact (chat);
935 if (old_remote_contact != remote_contact) {
936 /* The remote-contact associated with the chat changed, we need
937 * to keep track of any change of that contact and update the
938 * window each time. */
939 if (remote_contact) {
940 g_signal_connect_swapped (remote_contact, "notify",
941 G_CALLBACK (chat_window_update_chat_tab),
944 if (old_remote_contact) {
945 g_signal_handlers_disconnect_by_func (old_remote_contact,
946 chat_window_update_chat_tab,
950 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
951 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
954 chat_window_update_chat_tab (chat);
956 window = chat_window_find_chat (chat);
957 if (window != NULL) {
958 chat_window_update (window, FALSE);
963 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
964 EmpathySmiley *smiley,
967 EmpathyChatWindowPriv *priv = GET_PRIV (window);
969 GtkTextBuffer *buffer;
972 chat = priv->current_chat;
974 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
975 gtk_text_buffer_get_end_iter (buffer, &iter);
976 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
980 chat_window_conv_activate_cb (GtkAction *action,
981 EmpathyChatWindow *window)
983 EmpathyChatWindowPriv *priv = GET_PRIV (window);
986 EmpathyContact *remote_contact = NULL;
988 /* Favorite room menu */
989 is_room = empathy_chat_is_room (priv->current_chat);
993 gboolean found = FALSE;
994 EmpathyChatroom *chatroom;
996 room = empathy_chat_get_id (priv->current_chat);
997 account = empathy_chat_get_account (priv->current_chat);
998 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1000 if (chatroom != NULL)
1001 found = empathy_chatroom_is_favorite (chatroom);
1003 DEBUG ("This room %s favorite", found ? "is" : "is not");
1004 gtk_toggle_action_set_active (
1005 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1007 if (chatroom != NULL)
1008 found = empathy_chatroom_is_always_urgent (chatroom);
1010 gtk_toggle_action_set_active (
1011 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1014 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1015 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1017 /* Show contacts menu */
1018 g_object_get (priv->current_chat,
1019 "remote-contact", &remote_contact,
1020 "show-contacts", &active,
1022 if (remote_contact == NULL) {
1023 gtk_toggle_action_set_active (
1024 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1027 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1028 (remote_contact == NULL));
1029 if (remote_contact != NULL) {
1030 g_object_unref (remote_contact);
1035 chat_window_clear_activate_cb (GtkAction *action,
1036 EmpathyChatWindow *window)
1038 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1040 empathy_chat_clear (priv->current_chat);
1044 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1045 EmpathyChatWindow *window)
1047 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1052 EmpathyChatroom *chatroom;
1054 active = gtk_toggle_action_get_active (toggle_action);
1055 account = empathy_chat_get_account (priv->current_chat);
1056 room = empathy_chat_get_id (priv->current_chat);
1057 name = empathy_chat_dup_name (priv->current_chat);
1059 chatroom = empathy_chatroom_manager_ensure_chatroom (
1060 priv->chatroom_manager,
1065 empathy_chatroom_set_favorite (chatroom, active);
1066 g_object_unref (chatroom);
1071 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1072 EmpathyChatWindow *window)
1074 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1079 EmpathyChatroom *chatroom;
1081 active = gtk_toggle_action_get_active (toggle_action);
1082 account = empathy_chat_get_account (priv->current_chat);
1083 room = empathy_chat_get_id (priv->current_chat);
1084 name = empathy_chat_dup_name (priv->current_chat);
1086 chatroom = empathy_chatroom_manager_ensure_chatroom (
1087 priv->chatroom_manager,
1092 empathy_chatroom_set_always_urgent (chatroom, active);
1093 g_object_unref (chatroom);
1098 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1099 EmpathyChatWindow *window)
1101 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1104 active = gtk_toggle_action_get_active (toggle_action);
1106 empathy_chat_set_show_contacts (priv->current_chat, active);
1110 chat_window_invite_participant_activate_cb (GtkAction *action,
1111 EmpathyChatWindow *window)
1113 EmpathyChatWindowPriv *priv;
1115 EmpathyTpChat *tp_chat;
1118 priv = GET_PRIV (window);
1120 g_return_if_fail (priv->current_chat != NULL);
1122 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1124 dialog = empathy_invite_participant_dialog_new (
1125 GTK_WINDOW (priv->dialog), tp_chat);
1126 gtk_widget_show (dialog);
1128 response = gtk_dialog_run (GTK_DIALOG (dialog));
1130 if (response == GTK_RESPONSE_ACCEPT) {
1131 TpContact *tp_contact;
1132 EmpathyContact *contact;
1134 tp_contact = empathy_invite_participant_dialog_get_selected (
1135 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1136 if (tp_contact == NULL) goto out;
1138 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1140 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1141 contact, _("Inviting you to this room"));
1143 g_object_unref (contact);
1147 gtk_widget_destroy (dialog);
1151 chat_window_close_activate_cb (GtkAction *action,
1152 EmpathyChatWindow *window)
1154 EmpathyChatWindowPriv *priv;
1156 priv = GET_PRIV (window);
1158 g_return_if_fail (priv->current_chat != NULL);
1160 maybe_close_chat (window, priv->current_chat);
1164 chat_window_edit_activate_cb (GtkAction *action,
1165 EmpathyChatWindow *window)
1167 EmpathyChatWindowPriv *priv;
1168 GtkClipboard *clipboard;
1169 GtkTextBuffer *buffer;
1170 gboolean text_available;
1172 priv = GET_PRIV (window);
1174 g_return_if_fail (priv->current_chat != NULL);
1176 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1177 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1178 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1179 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1183 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1184 if (gtk_text_buffer_get_has_selection (buffer)) {
1185 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1186 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1190 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1192 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1193 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1196 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1197 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1198 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1202 chat_window_cut_activate_cb (GtkAction *action,
1203 EmpathyChatWindow *window)
1205 EmpathyChatWindowPriv *priv;
1207 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1209 priv = GET_PRIV (window);
1211 empathy_chat_cut (priv->current_chat);
1215 chat_window_copy_activate_cb (GtkAction *action,
1216 EmpathyChatWindow *window)
1218 EmpathyChatWindowPriv *priv;
1220 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1222 priv = GET_PRIV (window);
1224 empathy_chat_copy (priv->current_chat);
1228 chat_window_paste_activate_cb (GtkAction *action,
1229 EmpathyChatWindow *window)
1231 EmpathyChatWindowPriv *priv;
1233 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1235 priv = GET_PRIV (window);
1237 empathy_chat_paste (priv->current_chat);
1241 chat_window_find_activate_cb (GtkAction *action,
1242 EmpathyChatWindow *window)
1244 EmpathyChatWindowPriv *priv;
1246 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1248 priv = GET_PRIV (window);
1250 empathy_chat_find (priv->current_chat);
1254 chat_window_tabs_next_activate_cb (GtkAction *action,
1255 EmpathyChatWindow *window)
1257 EmpathyChatWindowPriv *priv;
1258 gint index_, numPages;
1259 gboolean wrap_around;
1261 priv = GET_PRIV (window);
1263 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1264 &wrap_around, NULL);
1266 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1267 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1269 if (index_ == (numPages - 1) && wrap_around) {
1270 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1274 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1278 chat_window_tabs_previous_activate_cb (GtkAction *action,
1279 EmpathyChatWindow *window)
1281 EmpathyChatWindowPriv *priv;
1282 gint index_, numPages;
1283 gboolean wrap_around;
1285 priv = GET_PRIV (window);
1287 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1288 &wrap_around, NULL);
1290 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1291 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1293 if (index_ <= 0 && wrap_around) {
1294 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1298 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1302 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1303 EmpathyChatWindow *window)
1305 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1306 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1307 empathy_get_current_action_time ());
1311 chat_window_tabs_left_activate_cb (GtkAction *action,
1312 EmpathyChatWindow *window)
1314 EmpathyChatWindowPriv *priv;
1316 gint index_, num_pages;
1318 priv = GET_PRIV (window);
1320 chat = priv->current_chat;
1321 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1326 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1330 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1331 chat_window_menu_context_update (priv, num_pages);
1335 chat_window_tabs_right_activate_cb (GtkAction *action,
1336 EmpathyChatWindow *window)
1338 EmpathyChatWindowPriv *priv;
1340 gint index_, num_pages;
1342 priv = GET_PRIV (window);
1344 chat = priv->current_chat;
1345 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1347 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1351 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1352 chat_window_menu_context_update (priv, num_pages);
1355 static EmpathyChatWindow *
1356 empathy_chat_window_new (void)
1358 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1362 chat_window_detach_activate_cb (GtkAction *action,
1363 EmpathyChatWindow *window)
1365 EmpathyChatWindowPriv *priv;
1366 EmpathyChatWindow *new_window;
1369 priv = GET_PRIV (window);
1371 chat = priv->current_chat;
1372 new_window = empathy_chat_window_new ();
1374 empathy_chat_window_move_chat (window, new_window, chat);
1376 priv = GET_PRIV (new_window);
1377 gtk_widget_show (priv->dialog);
1381 chat_window_help_contents_activate_cb (GtkAction *action,
1382 EmpathyChatWindow *window)
1384 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1386 empathy_url_show (priv->dialog, "ghelp:empathy");
1390 chat_window_help_about_activate_cb (GtkAction *action,
1391 EmpathyChatWindow *window)
1393 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1395 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1399 chat_window_delete_event_cb (GtkWidget *dialog,
1401 EmpathyChatWindow *window)
1403 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1404 EmpathyChat *chat = NULL;
1408 DEBUG ("Delete event received");
1410 for (l = priv->chats; l != NULL; l = l->next) {
1411 if (empathy_chat_is_room (l->data)) {
1418 confirm_close (window, TRUE, n_rooms,
1419 (n_rooms == 1 ? chat : NULL));
1421 remove_all_chats (window);
1428 chat_window_composing_cb (EmpathyChat *chat,
1429 gboolean is_composing,
1430 EmpathyChatWindow *window)
1432 chat_window_update_chat_tab (chat);
1436 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1439 EmpathyChatWindowPriv *priv;
1441 priv = GET_PRIV (window);
1443 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1447 chat_window_notification_closed_cb (NotifyNotification *notify,
1448 EmpathyChatWindow *self)
1450 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1452 g_object_unref (notify);
1453 if (priv->notification == notify) {
1454 priv->notification = NULL;
1459 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1460 EmpathyMessage *message,
1463 EmpathyContact *sender;
1464 const gchar *header;
1468 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1469 gboolean res, has_x_canonical_append;
1470 NotifyNotification *notification = priv->notification;
1472 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1475 res = g_settings_get_boolean (priv->gsettings_notif,
1476 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1483 sender = empathy_message_get_sender (message);
1484 header = empathy_contact_get_alias (sender);
1485 body = empathy_message_get_body (message);
1486 escaped = g_markup_escape_text (body, -1);
1487 has_x_canonical_append = empathy_notify_manager_has_capability (
1488 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1490 if (notification != NULL && !has_x_canonical_append) {
1491 /* if the notification server supports x-canonical-append, it is
1492 better to not use notify_notification_update to avoid
1493 overwriting the current notification message */
1494 notify_notification_update (notification,
1495 header, escaped, NULL);
1497 /* if the notification server supports x-canonical-append,
1498 the hint will be added, so that the message from the
1499 just created notification will be automatically appended
1500 to an existing notification with the same title.
1501 In this way the previous message will not be lost: the new
1502 message will appear below it, in the same notification */
1503 notification = notify_notification_new (header, escaped, NULL);
1505 if (priv->notification == NULL) {
1506 priv->notification = notification;
1509 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1511 tp_g_signal_connect_object (notification, "closed",
1512 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1514 if (has_x_canonical_append) {
1515 /* We have to set a not empty string to keep libnotify happy */
1516 notify_notification_set_hint_string (notification,
1517 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1520 notify_notification_set_hint (notification,
1521 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1522 g_variant_new_string ("im.received"));
1525 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1526 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1528 if (pixbuf != NULL) {
1529 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1530 g_object_unref (pixbuf);
1533 notify_notification_show (notification, NULL);
1539 chat_window_set_highlight_room_labels (EmpathyChat *chat)
1541 gchar *markup, *name;
1544 if (!empathy_chat_is_room (chat))
1547 name = empathy_chat_dup_name (chat);
1548 markup = g_markup_printf_escaped (
1549 "<span color=\"red\" weight=\"bold\">%s</span>",
1552 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1553 gtk_label_set_markup (GTK_LABEL (widget), markup);
1555 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1556 gtk_label_set_markup (GTK_LABEL (widget), markup);
1563 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1565 EmpathyChatWindowPriv *priv;
1568 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1570 priv = GET_PRIV (window);
1572 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1578 chat_window_new_message_cb (EmpathyChat *chat,
1579 EmpathyMessage *message,
1581 gboolean should_highlight,
1582 EmpathyChatWindow *window)
1584 EmpathyChatWindowPriv *priv;
1586 gboolean needs_urgency;
1587 EmpathyContact *sender;
1589 priv = GET_PRIV (window);
1591 has_focus = empathy_chat_window_has_focus (window);
1593 /* - if we're the sender, we play the sound if it's specified in the
1594 * preferences and we're not away.
1595 * - if we receive a message, we play the sound if it's specified in the
1596 * preferences and the window does not have focus on the chat receiving
1600 sender = empathy_message_get_sender (message);
1602 if (empathy_contact_is_user (sender)) {
1603 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1604 EMPATHY_SOUND_MESSAGE_OUTGOING);
1607 if (has_focus && priv->current_chat == chat) {
1608 /* window and tab are focused so consider the message to be read */
1610 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1611 empathy_chat_messages_read (chat);
1615 /* Update the chat tab if this is the first unread message */
1616 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1617 chat_window_update_chat_tab (chat);
1620 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1621 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1622 * an unamed MUC (msn-like).
1623 * In case of a MUC, we set urgency if either:
1624 * a) the chatroom's always_urgent property is TRUE
1625 * b) the message contains our alias
1627 if (empathy_chat_is_room (chat)) {
1630 EmpathyChatroom *chatroom;
1632 account = empathy_chat_get_account (chat);
1633 room = empathy_chat_get_id (chat);
1635 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1638 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1639 needs_urgency = TRUE;
1641 needs_urgency = should_highlight;
1644 needs_urgency = TRUE;
1647 if (needs_urgency) {
1648 chat_window_set_highlight_room_labels (chat);
1651 chat_window_set_urgency_hint (window, TRUE);
1654 /* Pending messages have already been displayed and notified in the
1655 * approver, so we don't display a notification and play a sound for those */
1657 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1658 EMPATHY_SOUND_MESSAGE_INCOMING);
1660 chat_window_show_or_update_notification (window, message, chat);
1664 /* update the number of unread messages and the window icon */
1665 chat_window_title_update (priv);
1666 chat_window_icon_update (priv, TRUE);
1670 chat_window_command_part (EmpathyChat *chat,
1673 EmpathyChat *chat_to_be_parted;
1674 EmpathyTpChat *tp_chat = NULL;
1676 if (strv[1] == NULL) {
1677 /* No chatroom ID specified */
1678 tp_chat = empathy_chat_get_tp_chat (chat);
1680 empathy_tp_chat_leave (tp_chat, "");
1683 chat_to_be_parted = empathy_chat_window_find_chat (
1684 empathy_chat_get_account (chat), strv[1], FALSE);
1686 if (chat_to_be_parted != NULL) {
1687 /* Found a chatroom matching the specified ID */
1688 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1690 empathy_tp_chat_leave (tp_chat, strv[2]);
1694 /* Going by the syntax of PART command:
1696 * /PART [<chatroom-ID>] [<reason>]
1698 * Chatroom-ID is not a must to specify a reason.
1699 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1700 * MUC then the current chatroom should be parted and srtv[1] should
1701 * be treated as part of the optional part-message. */
1702 message = g_strconcat (strv[1], " ", strv[2], NULL);
1703 tp_chat = empathy_chat_get_tp_chat (chat);
1705 empathy_tp_chat_leave (tp_chat, message);
1711 static GtkNotebook *
1712 notebook_create_window_cb (GtkNotebook *source,
1718 EmpathyChatWindowPriv *priv;
1719 EmpathyChatWindow *window, *new_window;
1722 chat = EMPATHY_CHAT (page);
1723 window = chat_window_find_chat (chat);
1725 new_window = empathy_chat_window_new ();
1726 priv = GET_PRIV (new_window);
1728 DEBUG ("Detach hook called");
1730 empathy_chat_window_move_chat (window, new_window, chat);
1732 gtk_widget_show (priv->dialog);
1733 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1739 chat_window_page_switched_cb (GtkNotebook *notebook,
1742 EmpathyChatWindow *window)
1744 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1745 EmpathyChat *chat = EMPATHY_CHAT (child);
1747 DEBUG ("Page switched");
1749 if (priv->page_added) {
1750 priv->page_added = FALSE;
1751 empathy_chat_scroll_down (chat);
1753 else if (priv->current_chat == chat) {
1757 priv->current_chat = chat;
1758 empathy_chat_messages_read (chat);
1760 chat_window_update_chat_tab (chat);
1764 chat_window_page_added_cb (GtkNotebook *notebook,
1767 EmpathyChatWindow *window)
1769 EmpathyChatWindowPriv *priv;
1772 priv = GET_PRIV (window);
1774 /* If we just received DND to the same window, we don't want
1775 * to do anything here like removing the tab and then readding
1776 * it, so we return here and in "page-added".
1778 if (priv->dnd_same_window) {
1779 DEBUG ("Page added (back to the same window)");
1780 priv->dnd_same_window = FALSE;
1784 DEBUG ("Page added");
1786 /* Get chat object */
1787 chat = EMPATHY_CHAT (child);
1789 /* Connect chat signals for this window */
1790 g_signal_connect (chat, "composing",
1791 G_CALLBACK (chat_window_composing_cb),
1793 g_signal_connect (chat, "new-message",
1794 G_CALLBACK (chat_window_new_message_cb),
1796 g_signal_connect (chat, "part-command-entered",
1797 G_CALLBACK (chat_window_command_part),
1799 g_signal_connect (chat, "notify::tp-chat",
1800 G_CALLBACK (chat_window_update_chat_tab),
1803 /* Set flag so we know to perform some special operations on
1804 * switch page due to the new page being added.
1806 priv->page_added = TRUE;
1808 /* Get list of chats up to date */
1809 priv->chats = g_list_append (priv->chats, chat);
1811 chat_window_update_chat_tab (chat);
1815 chat_window_page_removed_cb (GtkNotebook *notebook,
1818 EmpathyChatWindow *window)
1820 EmpathyChatWindowPriv *priv;
1823 priv = GET_PRIV (window);
1825 /* If we just received DND to the same window, we don't want
1826 * to do anything here like removing the tab and then readding
1827 * it, so we return here and in "page-added".
1829 if (priv->dnd_same_window) {
1830 DEBUG ("Page removed (and will be readded to same window)");
1834 DEBUG ("Page removed");
1836 /* Get chat object */
1837 chat = EMPATHY_CHAT (child);
1839 /* Disconnect all signal handlers for this chat and this window */
1840 g_signal_handlers_disconnect_by_func (chat,
1841 G_CALLBACK (chat_window_composing_cb),
1843 g_signal_handlers_disconnect_by_func (chat,
1844 G_CALLBACK (chat_window_new_message_cb),
1846 g_signal_handlers_disconnect_by_func (chat,
1847 G_CALLBACK (chat_window_update_chat_tab),
1850 /* Keep list of chats up to date */
1851 priv->chats = g_list_remove (priv->chats, chat);
1852 empathy_chat_messages_read (chat);
1854 if (priv->chats == NULL) {
1855 g_object_unref (window);
1857 chat_window_update (window, TRUE);
1862 chat_window_focus_in_event_cb (GtkWidget *widget,
1864 EmpathyChatWindow *window)
1866 EmpathyChatWindowPriv *priv;
1868 priv = GET_PRIV (window);
1870 empathy_chat_messages_read (priv->current_chat);
1872 chat_window_set_urgency_hint (window, FALSE);
1874 /* Update the title, since we now mark all unread messages as read. */
1875 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1881 chat_window_drag_drop (GtkWidget *widget,
1882 GdkDragContext *context,
1886 EmpathyChatWindow *window)
1889 EmpathyChatWindowPriv *priv;
1891 priv = GET_PRIV (window);
1893 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1894 if (target == GDK_NONE)
1895 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1897 if (target != GDK_NONE) {
1898 gtk_drag_get_data (widget, context, target, time_);
1906 chat_window_drag_motion (GtkWidget *widget,
1907 GdkDragContext *context,
1911 EmpathyChatWindow *window)
1914 EmpathyChatWindowPriv *priv;
1916 priv = GET_PRIV (window);
1918 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1919 if (target != GDK_NONE) {
1920 /* This is a file drag. Ensure the contact is online and set the
1921 drag type to COPY. Note that it's possible that the tab will
1922 be switched by GTK+ after a timeout from drag_motion without
1923 getting another drag_motion to disable the drop. You have
1924 to hold your mouse really still.
1926 EmpathyContact *contact;
1928 priv = GET_PRIV (window);
1929 contact = empathy_chat_get_remote_contact (priv->current_chat);
1930 /* contact is NULL for multi-user chats. We don't do
1931 * file transfers to MUCs. We also don't send files
1932 * to offline contacts or contacts that don't support
1935 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1936 gdk_drag_status (context, 0, time_);
1939 if (!(empathy_contact_get_capabilities (contact)
1940 & EMPATHY_CAPABILITIES_FT)) {
1941 gdk_drag_status (context, 0, time_);
1944 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1948 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1949 if (target != GDK_NONE) {
1950 /* This is a drag of a contact from a contact list. Set to COPY.
1951 FIXME: If this drag is to a MUC window, it invites the user.
1952 Otherwise, it opens a chat. Should we use a different drag
1953 type for invites? Should we allow ASK?
1955 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1963 chat_window_drag_data_received (GtkWidget *widget,
1964 GdkDragContext *context,
1967 GtkSelectionData *selection,
1970 EmpathyChatWindow *window)
1972 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1973 EmpathyChat *chat = NULL;
1974 EmpathyChatWindow *old_window;
1975 TpAccount *account = NULL;
1976 EmpathyClientFactory *factory;
1979 const gchar *account_id;
1980 const gchar *contact_id;
1982 id = (const gchar*) gtk_selection_data_get_data (selection);
1984 factory = empathy_client_factory_dup ();
1986 DEBUG ("DND contact from roster with id:'%s'", id);
1988 strv = g_strsplit (id, ":", 2);
1989 if (g_strv_length (strv) == 2) {
1990 account_id = strv[0];
1991 contact_id = strv[1];
1993 tp_simple_client_factory_ensure_account (
1994 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
1997 g_object_unref (factory);
1998 if (account != NULL)
1999 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2002 if (account == NULL) {
2004 gtk_drag_finish (context, FALSE, FALSE, time_);
2009 empathy_chat_with_contact_id (
2010 account, contact_id,
2011 empathy_get_current_action_time (),
2019 old_window = chat_window_find_chat (chat);
2021 if (old_window == window) {
2022 gtk_drag_finish (context, TRUE, FALSE, time_);
2026 empathy_chat_window_move_chat (old_window, window, chat);
2028 empathy_chat_window_add_chat (window, chat);
2031 /* Added to take care of any outstanding chat events */
2032 empathy_chat_window_present_chat (chat,
2033 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2035 /* We should return TRUE to remove the data when doing
2036 * GDK_ACTION_MOVE, but we don't here otherwise it has
2037 * weird consequences, and we handle that internally
2038 * anyway with add_chat () and remove_chat ().
2040 gtk_drag_finish (context, TRUE, FALSE, time_);
2042 else if (info == DND_DRAG_TYPE_URI_LIST) {
2043 EmpathyChatWindowPriv *priv;
2044 EmpathyContact *contact;
2047 priv = GET_PRIV (window);
2048 contact = empathy_chat_get_remote_contact (priv->current_chat);
2050 /* contact is NULL when current_chat is a multi-user chat.
2051 * We don't do file transfers to MUCs, so just cancel the drag.
2053 if (contact == NULL) {
2054 gtk_drag_finish (context, TRUE, FALSE, time_);
2058 data = (const gchar *) gtk_selection_data_get_data (selection);
2059 empathy_send_file_from_uri_list (contact, data);
2061 gtk_drag_finish (context, TRUE, FALSE, time_);
2063 else if (info == DND_DRAG_TYPE_TAB) {
2065 EmpathyChatWindow *old_window = NULL;
2069 chat = (void *) gtk_selection_data_get_data (selection);
2070 old_window = chat_window_find_chat (*chat);
2073 EmpathyChatWindowPriv *priv;
2075 priv = GET_PRIV (window);
2076 priv->dnd_same_window = (old_window == window);
2077 DEBUG ("DND tab (within same window: %s)",
2078 priv->dnd_same_window ? "Yes" : "No");
2081 DEBUG ("DND from unknown source");
2082 gtk_drag_finish (context, FALSE, FALSE, time_);
2087 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2088 guint num_chats_in_manager,
2089 EmpathyChatWindow *window)
2091 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2093 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2094 num_chats_in_manager > 0);
2098 chat_window_finalize (GObject *object)
2100 EmpathyChatWindow *window;
2101 EmpathyChatWindowPriv *priv;
2103 window = EMPATHY_CHAT_WINDOW (object);
2104 priv = GET_PRIV (window);
2106 DEBUG ("Finalized: %p", object);
2108 g_object_unref (priv->ui_manager);
2109 g_object_unref (priv->chatroom_manager);
2110 g_object_unref (priv->notify_mgr);
2111 g_object_unref (priv->gsettings_chat);
2112 g_object_unref (priv->gsettings_notif);
2113 g_object_unref (priv->gsettings_ui);
2114 g_object_unref (priv->sound_mgr);
2116 if (priv->notification != NULL) {
2117 notify_notification_close (priv->notification, NULL);
2118 priv->notification = NULL;
2121 if (priv->contact_targets) {
2122 gtk_target_list_unref (priv->contact_targets);
2124 if (priv->file_targets) {
2125 gtk_target_list_unref (priv->file_targets);
2128 if (priv->chat_manager) {
2129 g_signal_handler_disconnect (priv->chat_manager,
2130 priv->chat_manager_chats_changed_id);
2131 g_object_unref (priv->chat_manager);
2132 priv->chat_manager = NULL;
2135 chat_windows = g_list_remove (chat_windows, window);
2136 gtk_widget_destroy (priv->dialog);
2138 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2142 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2144 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2146 object_class->finalize = chat_window_finalize;
2148 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2152 empathy_chat_window_init (EmpathyChatWindow *window)
2155 GtkAccelGroup *accel_group;
2160 GtkWidget *chat_vbox;
2162 EmpathySmileyManager *smiley_manager;
2163 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2164 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2166 window->priv = priv;
2167 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2168 gui = empathy_builder_get_file (filename,
2169 "chat_window", &priv->dialog,
2170 "chat_vbox", &chat_vbox,
2171 "ui_manager", &priv->ui_manager,
2172 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2173 "menu_conv_favorite", &priv->menu_conv_favorite,
2174 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2175 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2176 "menu_edit_cut", &priv->menu_edit_cut,
2177 "menu_edit_copy", &priv->menu_edit_copy,
2178 "menu_edit_paste", &priv->menu_edit_paste,
2179 "menu_edit_find", &priv->menu_edit_find,
2180 "menu_tabs_next", &priv->menu_tabs_next,
2181 "menu_tabs_prev", &priv->menu_tabs_prev,
2182 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2183 "menu_tabs_left", &priv->menu_tabs_left,
2184 "menu_tabs_right", &priv->menu_tabs_right,
2185 "menu_tabs_detach", &priv->menu_tabs_detach,
2189 empathy_builder_connect (gui, window,
2190 "menu_conv", "activate", chat_window_conv_activate_cb,
2191 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2192 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2193 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2194 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2195 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2196 "menu_conv_close", "activate", chat_window_close_activate_cb,
2197 "menu_edit", "activate", chat_window_edit_activate_cb,
2198 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2199 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2200 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2201 "menu_edit_find", "activate", chat_window_find_activate_cb,
2202 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2203 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2204 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2205 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2206 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2207 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2208 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2209 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2212 g_object_ref (priv->ui_manager);
2213 g_object_unref (gui);
2215 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2216 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2217 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2218 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2220 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2222 priv->notebook = gtk_notebook_new ();
2224 g_signal_connect (priv->notebook, "create-window",
2225 G_CALLBACK (notebook_create_window_cb), window);
2227 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2228 "EmpathyChatWindow");
2229 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2230 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2231 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2232 gtk_widget_show (priv->notebook);
2235 accel_group = gtk_accel_group_new ();
2236 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2238 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2239 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2242 gtk_accel_group_connect (accel_group,
2249 g_object_unref (accel_group);
2251 /* Set up drag target lists */
2252 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2253 G_N_ELEMENTS (drag_types_dest_contact));
2254 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2255 G_N_ELEMENTS (drag_types_dest_file));
2257 /* Set up smiley menu */
2258 smiley_manager = empathy_smiley_manager_dup_singleton ();
2259 submenu = empathy_smiley_menu_new (smiley_manager,
2260 chat_window_insert_smiley_activate_cb,
2262 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2263 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2264 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2265 g_object_unref (smiley_manager);
2267 /* Set up signals we can't do with ui file since we may need to
2268 * block/unblock them at some later stage.
2271 g_signal_connect (priv->dialog,
2273 G_CALLBACK (chat_window_delete_event_cb),
2275 g_signal_connect (priv->dialog,
2277 G_CALLBACK (chat_window_focus_in_event_cb),
2279 g_signal_connect_after (priv->notebook,
2281 G_CALLBACK (chat_window_page_switched_cb),
2283 g_signal_connect (priv->notebook,
2285 G_CALLBACK (chat_window_page_added_cb),
2287 g_signal_connect (priv->notebook,
2289 G_CALLBACK (chat_window_page_removed_cb),
2292 /* Set up drag and drop */
2293 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2294 GTK_DEST_DEFAULT_HIGHLIGHT,
2296 G_N_ELEMENTS (drag_types_dest),
2297 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2299 /* connect_after to allow GtkNotebook's built-in tab switching */
2300 g_signal_connect_after (priv->notebook,
2302 G_CALLBACK (chat_window_drag_motion),
2304 g_signal_connect (priv->notebook,
2305 "drag-data-received",
2306 G_CALLBACK (chat_window_drag_data_received),
2308 g_signal_connect (priv->notebook,
2310 G_CALLBACK (chat_window_drag_drop),
2313 chat_windows = g_list_prepend (chat_windows, window);
2315 /* Set up private details */
2317 priv->current_chat = NULL;
2318 priv->notification = NULL;
2320 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2322 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2323 priv->chat_manager_chats_changed_id =
2324 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2325 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2328 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2329 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2333 /* Returns the window to open a new tab in if there is a suitable window,
2334 * otherwise, returns NULL indicating that a new window should be added.
2336 static EmpathyChatWindow *
2337 empathy_chat_window_get_default (gboolean room)
2339 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2341 gboolean separate_windows = TRUE;
2343 separate_windows = g_settings_get_boolean (gsettings,
2344 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2346 g_object_unref (gsettings);
2348 if (separate_windows) {
2349 /* Always create a new window */
2353 for (l = chat_windows; l; l = l->next) {
2354 EmpathyChatWindow *chat_window;
2355 guint nb_rooms, nb_private;
2357 chat_window = l->data;
2359 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2361 /* Skip the window if there aren't any rooms in it */
2362 if (room && nb_rooms == 0)
2365 /* Skip the window if there aren't any 1-1 chats in it */
2366 if (!room && nb_private == 0)
2376 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2379 EmpathyChatWindowPriv *priv;
2381 GtkWidget *popup_label;
2383 GValue value = { 0, };
2385 g_return_if_fail (window != NULL);
2386 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2388 priv = GET_PRIV (window);
2390 /* Reference the chat object */
2391 g_object_ref (chat);
2393 /* If this window has just been created, position it */
2394 if (priv->chats == NULL) {
2395 const gchar *name = "chat-window";
2396 gboolean separate_windows;
2398 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2399 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2401 if (empathy_chat_is_room (chat))
2402 name = "room-window";
2404 if (separate_windows) {
2407 /* Save current position of the window */
2408 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2410 /* First bind to the 'generic' name. So new window for which we didn't
2411 * save a geometry yet will have the geometry of the last saved
2412 * window (bgo #601191). */
2413 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2415 /* Restore previous position of the window so the newly created window
2416 * won't be in the same position as the latest saved window and so
2417 * completely hide it. */
2418 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2420 /* Then bind it to the name of the contact/room so we'll save the
2421 * geometry specific to this window */
2422 name = empathy_chat_get_id (chat);
2425 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2428 child = GTK_WIDGET (chat);
2429 label = chat_window_create_label (window, chat, TRUE);
2430 popup_label = chat_window_create_label (window, chat, FALSE);
2431 gtk_widget_show (child);
2433 g_signal_connect (chat, "notify::name",
2434 G_CALLBACK (chat_window_chat_notify_cb),
2436 g_signal_connect (chat, "notify::subject",
2437 G_CALLBACK (chat_window_chat_notify_cb),
2439 g_signal_connect (chat, "notify::remote-contact",
2440 G_CALLBACK (chat_window_chat_notify_cb),
2442 g_signal_connect (chat, "notify::sms-channel",
2443 G_CALLBACK (chat_window_chat_notify_cb),
2445 g_signal_connect (chat, "notify::n-messages-sending",
2446 G_CALLBACK (chat_window_chat_notify_cb),
2448 g_signal_connect (chat, "notify::nb-unread-messages",
2449 G_CALLBACK (chat_window_chat_notify_cb),
2451 chat_window_chat_notify_cb (chat);
2453 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2454 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2455 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2456 g_value_init (&value, G_TYPE_BOOLEAN);
2457 g_value_set_boolean (&value, TRUE);
2458 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2459 child, "tab-expand" , &value);
2460 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2461 child, "tab-fill" , &value);
2462 g_value_unset (&value);
2464 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2468 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2471 EmpathyChatWindowPriv *priv;
2473 EmpathyContact *remote_contact;
2474 EmpathyChatManager *chat_manager;
2476 g_return_if_fail (window != NULL);
2477 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2479 priv = GET_PRIV (window);
2481 g_signal_handlers_disconnect_by_func (chat,
2482 chat_window_chat_notify_cb,
2484 remote_contact = g_object_get_data (G_OBJECT (chat),
2485 "chat-window-remote-contact");
2486 if (remote_contact) {
2487 g_signal_handlers_disconnect_by_func (remote_contact,
2488 chat_window_update_chat_tab,
2492 chat_manager = empathy_chat_manager_dup_singleton ();
2493 empathy_chat_manager_closed_chat (chat_manager, chat);
2494 g_object_unref (chat_manager);
2496 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2498 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2500 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2502 g_object_unref (chat);
2506 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2507 EmpathyChatWindow *new_window,
2512 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2513 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2514 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2516 widget = GTK_WIDGET (chat);
2518 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2519 G_OBJECT (widget)->ref_count);
2521 /* We reference here to make sure we don't loose the widget
2522 * and the EmpathyChat object during the move.
2524 g_object_ref (chat);
2525 g_object_ref (widget);
2527 empathy_chat_window_remove_chat (old_window, chat);
2528 empathy_chat_window_add_chat (new_window, chat);
2530 g_object_unref (widget);
2531 g_object_unref (chat);
2535 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2538 EmpathyChatWindowPriv *priv;
2541 g_return_if_fail (window != NULL);
2542 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2544 priv = GET_PRIV (window);
2546 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2548 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2553 empathy_chat_window_find_chat (TpAccount *account,
2555 gboolean sms_channel)
2559 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2561 for (l = chat_windows; l; l = l->next) {
2562 EmpathyChatWindowPriv *priv;
2563 EmpathyChatWindow *window;
2567 priv = GET_PRIV (window);
2569 for (ll = priv->chats; ll; ll = ll->next) {
2574 if (account == empathy_chat_get_account (chat) &&
2575 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2576 sms_channel == empathy_chat_is_sms_channel (chat)) {
2586 empathy_chat_window_present_chat (EmpathyChat *chat,
2589 EmpathyChatWindow *window;
2590 EmpathyChatWindowPriv *priv;
2591 guint32 x_timestamp;
2593 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2595 window = chat_window_find_chat (chat);
2597 /* If the chat has no window, create one */
2598 if (window == NULL) {
2599 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2601 window = empathy_chat_window_new ();
2603 /* we want to display the newly created window even if we don't present
2605 priv = GET_PRIV (window);
2606 gtk_widget_show (priv->dialog);
2609 empathy_chat_window_add_chat (window, chat);
2612 /* Don't force the window to show itself when it wasn't
2613 * an action by the user
2615 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2618 priv = GET_PRIV (window);
2620 if (x_timestamp != GDK_CURRENT_TIME) {
2621 /* Don't present or switch tab if the action was earlier than the
2622 * last actions X time, accounting for overflow and the first ever
2625 if (priv->x_user_action_time != 0
2626 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2629 priv->x_user_action_time = x_timestamp;
2632 empathy_chat_window_switch_to_chat (window, chat);
2634 /* Don't use empathy_window_present_with_time () which would move the window
2635 * to our current desktop but move to the window's desktop instead. This is
2636 * more coherent with Shell's 'app is ready' notication which moves the view
2637 * to the app desktop rather than moving the app itself. */
2638 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2640 gtk_widget_grab_focus (chat->input_text_view);
2644 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2648 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2650 guint _nb_rooms = 0, _nb_private = 0;
2652 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2653 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2659 if (nb_rooms != NULL)
2660 *nb_rooms = _nb_rooms;
2661 if (nb_private != NULL)
2662 *nb_private = _nb_private;