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-request-util.h>
48 #include <libempathy/empathy-individual-manager.h>
50 #include <libempathy-gtk/empathy-images.h>
51 #include <libempathy-gtk/empathy-contact-dialogs.h>
52 #include <libempathy-gtk/empathy-log-window.h>
53 #include <libempathy-gtk/empathy-geometry.h>
54 #include <libempathy-gtk/empathy-smiley-manager.h>
55 #include <libempathy-gtk/empathy-sound-manager.h>
56 #include <libempathy-gtk/empathy-ui-utils.h>
57 #include <libempathy-gtk/empathy-notify-manager.h>
59 #include "empathy-chat-manager.h"
60 #include "empathy-chat-window.h"
61 #include "empathy-about-dialog.h"
62 #include "empathy-invite-participant-dialog.h"
63 #include "gedit-close-button.h"
65 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
66 #include <libempathy/empathy-debug.h>
68 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
70 #define X_EARLIER_OR_EQL(t1, t2) \
71 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
72 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
75 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
77 EmpathyChat *current_chat;
80 gboolean dnd_same_window;
81 EmpathyChatroomManager *chatroom_manager;
82 EmpathyNotifyManager *notify_mgr;
85 NotifyNotification *notification;
87 GtkTargetList *contact_targets;
88 GtkTargetList *file_targets;
90 EmpathyChatManager *chat_manager;
91 gulong chat_manager_chats_changed_id;
94 GtkUIManager *ui_manager;
95 GtkAction *menu_conv_insert_smiley;
96 GtkAction *menu_conv_favorite;
97 GtkAction *menu_conv_always_urgent;
98 GtkAction *menu_conv_toggle_contacts;
100 GtkAction *menu_edit_cut;
101 GtkAction *menu_edit_copy;
102 GtkAction *menu_edit_paste;
103 GtkAction *menu_edit_find;
105 GtkAction *menu_tabs_next;
106 GtkAction *menu_tabs_prev;
107 GtkAction *menu_tabs_undo_close_tab;
108 GtkAction *menu_tabs_left;
109 GtkAction *menu_tabs_right;
110 GtkAction *menu_tabs_detach;
112 /* Last user action time we acted upon to show a tab */
113 guint32 x_user_action_time;
115 GSettings *gsettings_chat;
116 GSettings *gsettings_notif;
117 GSettings *gsettings_ui;
119 EmpathySoundManager *sound_mgr;
120 } EmpathyChatWindowPriv;
122 static GList *chat_windows = NULL;
124 static const guint tab_accel_keys[] = {
125 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
126 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
130 DND_DRAG_TYPE_CONTACT_ID,
131 DND_DRAG_TYPE_INDIVIDUAL_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 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
139 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
140 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
141 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
144 static const GtkTargetEntry drag_types_dest_contact[] = {
145 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
146 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
149 static const GtkTargetEntry drag_types_dest_file[] = {
150 /* must be first to be prioritized, in order to receive the
151 * note's file path from Tomboy instead of an URI */
152 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
153 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
156 static void chat_window_update (EmpathyChatWindow *window,
157 gboolean update_contact_menu);
159 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
162 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
165 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
166 EmpathyChatWindow *new_window,
169 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
173 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
176 chat_window_accel_cb (GtkAccelGroup *accelgroup,
180 EmpathyChatWindow *window)
182 EmpathyChatWindowPriv *priv;
186 priv = GET_PRIV (window);
188 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
189 if (tab_accel_keys[i] == key) {
196 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
200 static EmpathyChatWindow *
201 chat_window_find_chat (EmpathyChat *chat)
203 EmpathyChatWindowPriv *priv;
206 for (l = chat_windows; l; l = l->next) {
207 priv = GET_PRIV (l->data);
208 ll = g_list_find (priv->chats, chat);
218 remove_all_chats (EmpathyChatWindow *window)
220 EmpathyChatWindowPriv *priv;
222 priv = GET_PRIV (window);
223 g_object_ref (window);
225 while (priv->chats) {
226 empathy_chat_window_remove_chat (window, priv->chats->data);
229 g_object_unref (window);
233 confirm_close_response_cb (GtkWidget *dialog,
235 EmpathyChatWindow *window)
239 chat = g_object_get_data (G_OBJECT (dialog), "chat");
241 gtk_widget_destroy (dialog);
243 if (response != GTK_RESPONSE_ACCEPT)
247 empathy_chat_window_remove_chat (window, chat);
249 remove_all_chats (window);
254 confirm_close (EmpathyChatWindow *window,
255 gboolean close_window,
259 EmpathyChatWindowPriv *priv;
261 gchar *primary, *secondary;
263 g_return_if_fail (n_rooms > 0);
266 g_return_if_fail (chat == NULL);
268 g_return_if_fail (chat != NULL);
271 priv = GET_PRIV (window);
273 /* If there are no chats in this window, how could we possibly have got
276 g_return_if_fail (priv->chats != NULL);
278 /* Treat closing a window which only has one tab exactly like closing
281 if (close_window && priv->chats->next == NULL) {
282 close_window = FALSE;
283 chat = priv->chats->data;
287 primary = g_strdup (_("Close this window?"));
290 gchar *chat_name = empathy_chat_dup_name (chat);
291 secondary = g_strdup_printf (
292 _("Closing this window will leave %s. You will "
293 "not receive any further messages until you "
298 secondary = g_strdup_printf (
299 /* Note to translators: the number of chats will
300 * always be at least 2.
303 "Closing this window will leave a chat room. You will "
304 "not receive any further messages until you rejoin it.",
305 "Closing this window will leave %u chat rooms. You will "
306 "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 (_("You will not receive any further messages from this chat "
314 "room until you rejoin it."));
318 dialog = gtk_message_dialog_new (
319 GTK_WINDOW (priv->dialog),
320 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
325 gtk_window_set_title (GTK_WINDOW (dialog), "");
326 g_object_set (dialog, "secondary-text", secondary, NULL);
331 gtk_dialog_add_button (GTK_DIALOG (dialog),
332 close_window ? _("Close window") : _("Leave room"),
333 GTK_RESPONSE_ACCEPT);
334 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
335 GTK_RESPONSE_ACCEPT);
338 g_object_set_data (G_OBJECT (dialog), "chat", chat);
341 g_signal_connect (dialog, "response",
342 G_CALLBACK (confirm_close_response_cb), window);
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);
373 chat_window_close_clicked_cb (GtkAction *action,
376 EmpathyChatWindow *window;
378 window = chat_window_find_chat (chat);
379 maybe_close_chat (window, chat);
383 chat_tab_style_updated_cb (GtkWidget *hbox,
387 int char_width, h, w;
388 PangoContext *context;
389 const PangoFontDescription *font_desc;
390 PangoFontMetrics *metrics;
392 button = g_object_get_data (G_OBJECT (user_data),
393 "chat-window-tab-close-button");
394 context = gtk_widget_get_pango_context (hbox);
396 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
397 GTK_STATE_FLAG_NORMAL);
399 metrics = pango_context_get_metrics (context, font_desc,
400 pango_context_get_language (context));
401 char_width = pango_font_metrics_get_approximate_char_width (metrics);
402 pango_font_metrics_unref (metrics);
404 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
405 GTK_ICON_SIZE_MENU, &w, &h);
407 /* Request at least about 12 chars width plus at least space for the status
408 * image and the close button */
409 gtk_widget_set_size_request (hbox,
410 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
412 gtk_widget_set_size_request (button, w, h);
416 chat_window_create_label (EmpathyChatWindow *window,
418 gboolean is_tab_label)
421 GtkWidget *name_label;
422 GtkWidget *status_image;
423 GtkWidget *event_box;
424 GtkWidget *event_box_hbox;
425 PangoAttrList *attr_list;
426 PangoAttribute *attr;
428 /* The spacing between the button and the label. */
429 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
431 event_box = gtk_event_box_new ();
432 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
434 name_label = gtk_label_new (NULL);
436 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
438 attr_list = pango_attr_list_new ();
439 attr = pango_attr_scale_new (1/1.2);
440 attr->start_index = 0;
441 attr->end_index = -1;
442 pango_attr_list_insert (attr_list, attr);
443 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
444 pango_attr_list_unref (attr_list);
446 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
447 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
448 g_object_set_data (G_OBJECT (chat),
449 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
452 status_image = gtk_image_new ();
454 /* Spacing between the icon and label. */
455 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
457 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
458 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
460 g_object_set_data (G_OBJECT (chat),
461 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
463 g_object_set_data (G_OBJECT (chat),
464 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
467 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
468 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
471 GtkWidget *close_button;
472 GtkWidget *sending_spinner;
474 sending_spinner = gtk_spinner_new ();
476 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
478 g_object_set_data (G_OBJECT (chat),
479 "chat-window-tab-sending-spinner",
482 close_button = gedit_close_button_new ();
483 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
485 /* We don't want focus/keynav for the button to avoid clutter, and
486 * Ctrl-W works anyway.
488 gtk_widget_set_can_focus (close_button, FALSE);
489 gtk_widget_set_can_default (close_button, FALSE);
491 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
493 g_signal_connect (close_button,
495 G_CALLBACK (chat_window_close_clicked_cb),
498 /* React to theme changes and also setup the size correctly. */
499 g_signal_connect (hbox,
501 G_CALLBACK (chat_tab_style_updated_cb),
505 gtk_widget_show_all (hbox);
511 _submenu_notify_visible_changed_cb (GObject *object,
515 g_signal_handlers_disconnect_by_func (object,
516 _submenu_notify_visible_changed_cb,
518 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
522 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
527 gboolean wrap_around;
528 gboolean is_connected;
531 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
532 first_page = (page_num == 0);
533 last_page = (page_num == (num_pages - 1));
534 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
536 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
538 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
540 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
542 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
543 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
544 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
545 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
549 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
550 EmpathyChatWindow *self)
552 EmpathyTpChat *tp_chat;
553 TpConnection *connection;
555 gboolean sensitive = FALSE;
557 g_return_if_fail (priv->current_chat != NULL);
559 action = gtk_ui_manager_get_action (priv->ui_manager,
560 "/chats_menubar/menu_conv/menu_conv_invite_participant");
561 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
563 if (tp_chat != NULL) {
564 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
566 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
567 (tp_connection_get_status (connection, NULL) ==
568 TP_CONNECTION_STATUS_CONNECTED);
571 gtk_action_set_sensitive (action, sensitive);
575 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
576 EmpathyChatWindow *window)
578 GtkWidget *menu, *submenu, *orig_submenu;
580 menu = gtk_ui_manager_get_widget (priv->ui_manager,
581 "/chats_menubar/menu_contact");
582 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
584 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
585 submenu = empathy_chat_get_contact_menu (priv->current_chat);
587 if (submenu != NULL) {
588 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
589 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
591 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
592 gtk_widget_show (menu);
593 gtk_widget_set_sensitive (menu, TRUE);
595 gtk_widget_set_sensitive (menu, FALSE);
598 tp_g_signal_connect_object (orig_submenu,
600 (GCallback)_submenu_notify_visible_changed_cb,
606 get_all_unread_messages (EmpathyChatWindowPriv *priv)
611 for (l = priv->chats; l != NULL; l = g_list_next (l))
612 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
618 get_window_title_name (EmpathyChatWindowPriv *priv)
620 gchar *active_name, *ret;
622 guint current_unread_msgs;
624 nb_chats = g_list_length (priv->chats);
625 g_assert (nb_chats > 0);
627 active_name = empathy_chat_dup_name (priv->current_chat);
629 current_unread_msgs = empathy_chat_get_nb_unread_messages (
634 if (current_unread_msgs == 0)
635 ret = g_strdup (active_name);
637 ret = g_strdup_printf (ngettext (
639 "%s (%d unread)", current_unread_msgs),
640 active_name, current_unread_msgs);
642 guint nb_others = nb_chats - 1;
643 guint all_unread_msgs;
645 all_unread_msgs = get_all_unread_messages (priv);
647 if (all_unread_msgs == 0) {
648 /* no unread message */
649 ret = g_strdup_printf (ngettext (
651 "%s (and %u others)", nb_others),
652 active_name, nb_others);
655 else if (all_unread_msgs == current_unread_msgs) {
656 /* unread messages are in the current tab */
657 ret = g_strdup_printf (ngettext (
659 "%s (%d unread)", current_unread_msgs),
660 active_name, current_unread_msgs);
663 else if (current_unread_msgs == 0) {
664 /* unread messages are in other tabs */
665 ret = g_strdup_printf (ngettext (
666 "%s (%d unread from others)",
667 "%s (%d unread from others)",
669 active_name, all_unread_msgs);
673 /* unread messages are in all the tabs */
674 ret = g_strdup_printf (ngettext (
675 "%s (%d unread from all)",
676 "%s (%d unread from all)",
678 active_name, all_unread_msgs);
682 g_free (active_name);
688 chat_window_title_update (EmpathyChatWindowPriv *priv)
692 name = get_window_title_name (priv);
693 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
698 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
701 EmpathyContact *remote_contact;
702 gboolean avatar_in_icon;
705 n_chats = g_list_length (priv->chats);
707 /* Update window icon */
709 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
710 EMPATHY_IMAGE_MESSAGE);
712 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
713 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
715 if (n_chats == 1 && avatar_in_icon) {
716 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
717 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
718 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
721 g_object_unref (icon);
724 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
730 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
734 GtkWidget *chat_close_button;
737 if (num_pages == 1) {
738 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
739 chat_close_button = g_object_get_data (G_OBJECT (chat),
740 "chat-window-tab-close-button");
741 gtk_widget_hide (chat_close_button);
743 for (i=0; i<num_pages; i++) {
744 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
745 chat_close_button = g_object_get_data (G_OBJECT (chat),
746 "chat-window-tab-close-button");
747 gtk_widget_show (chat_close_button);
753 chat_window_update (EmpathyChatWindow *window,
754 gboolean update_contact_menu)
756 EmpathyChatWindowPriv *priv = GET_PRIV (window);
759 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
761 /* Update Tab menu */
762 chat_window_menu_context_update (priv,
765 chat_window_conversation_menu_update (priv, window);
767 /* If this update is due to a focus-in event, we know the menu will be
768 the same as when we last left it, so no work to do. Besides, if we
769 swap out the menu on a focus-in, we may confuse any external global
771 if (update_contact_menu) {
772 chat_window_contact_menu_update (priv,
776 chat_window_title_update (priv);
778 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
780 chat_window_close_button_update (priv,
785 append_markup_printf (GString *string,
792 va_start (args, format);
794 tmp = g_markup_vprintf_escaped (format, args);
795 g_string_append (string, tmp);
802 chat_window_update_chat_tab_full (EmpathyChat *chat,
803 gboolean update_contact_menu)
805 EmpathyChatWindow *window;
806 EmpathyChatWindowPriv *priv;
807 EmpathyContact *remote_contact;
811 const gchar *subject;
812 const gchar *status = NULL;
816 const gchar *icon_name;
817 GtkWidget *tab_image;
818 GtkWidget *menu_image;
819 GtkWidget *sending_spinner;
822 window = chat_window_find_chat (chat);
826 priv = GET_PRIV (window);
828 /* Get information */
829 name = empathy_chat_dup_name (chat);
830 account = empathy_chat_get_account (chat);
831 subject = empathy_chat_get_subject (chat);
832 remote_contact = empathy_chat_get_remote_contact (chat);
834 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
835 name, tp_proxy_get_object_path (account), subject, remote_contact);
837 /* Update tab image */
838 if (empathy_chat_get_tp_chat (chat) == NULL) {
839 /* No TpChat, we are disconnected */
842 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
843 icon_name = EMPATHY_IMAGE_MESSAGE;
845 else if (remote_contact && empathy_chat_is_composing (chat)) {
846 icon_name = EMPATHY_IMAGE_TYPING;
848 else if (empathy_chat_is_sms_channel (chat)) {
849 icon_name = EMPATHY_IMAGE_SMS;
851 else if (remote_contact) {
852 icon_name = empathy_icon_name_for_contact (remote_contact);
854 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
857 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
858 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
859 if (icon_name != NULL) {
860 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
861 gtk_widget_show (tab_image);
862 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
863 gtk_widget_show (menu_image);
865 gtk_widget_hide (tab_image);
866 gtk_widget_hide (menu_image);
869 /* Update the sending spinner */
870 nb_sending = empathy_chat_get_n_messages_sending (chat);
871 sending_spinner = g_object_get_data (G_OBJECT (chat),
872 "chat-window-tab-sending-spinner");
874 g_object_set (sending_spinner,
875 "active", nb_sending > 0,
876 "visible", nb_sending > 0,
879 /* Update tab tooltip */
880 tooltip = g_string_new (NULL);
882 if (remote_contact) {
883 id = empathy_contact_get_id (remote_contact);
884 status = empathy_contact_get_presence_message (remote_contact);
889 if (empathy_chat_is_sms_channel (chat)) {
890 append_markup_printf (tooltip, "%s ", _("SMS:"));
893 append_markup_printf (tooltip,
894 "<b>%s</b><small> (%s)</small>",
896 tp_account_get_display_name (account));
898 if (nb_sending > 0) {
899 char *tmp = g_strdup_printf (
900 ngettext ("Sending %d message",
901 "Sending %d messages",
905 g_string_append (tooltip, "\n");
906 g_string_append (tooltip, tmp);
908 gtk_widget_set_tooltip_text (sending_spinner, tmp);
912 if (!EMP_STR_EMPTY (status)) {
913 append_markup_printf (tooltip, "\n<i>%s</i>", status);
917 append_markup_printf (tooltip, "\n<b>%s</b> %s",
918 _("Topic:"), subject);
921 if (remote_contact && empathy_chat_is_composing (chat)) {
922 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
925 if (remote_contact != NULL) {
926 const gchar * const *types;
928 types = empathy_contact_get_client_types (remote_contact);
929 if (types != NULL && !tp_strdiff (types[0], "phone")) {
930 /* I'm on a phone ! */
933 name = g_strdup_printf ("☎ %s", name);
938 markup = g_string_free (tooltip, FALSE);
939 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
940 gtk_widget_set_tooltip_markup (widget, markup);
941 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
942 gtk_widget_set_tooltip_markup (widget, markup);
945 /* Update tab and menu label */
946 if (empathy_chat_is_highlighted (chat)) {
947 markup = g_markup_printf_escaped (
948 "<span color=\"red\" weight=\"bold\">%s</span>",
951 markup = g_markup_escape_text (name, -1);
954 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
955 gtk_label_set_markup (GTK_LABEL (widget), markup);
956 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
957 gtk_label_set_markup (GTK_LABEL (widget), markup);
960 /* Update the window if it's the current chat */
961 if (priv->current_chat == chat) {
962 chat_window_update (window, update_contact_menu);
969 chat_window_update_chat_tab (EmpathyChat *chat)
971 chat_window_update_chat_tab_full (chat, TRUE);
975 chat_window_chat_notify_cb (EmpathyChat *chat)
977 EmpathyChatWindow *window;
978 EmpathyContact *old_remote_contact;
979 EmpathyContact *remote_contact = NULL;
981 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
982 remote_contact = empathy_chat_get_remote_contact (chat);
984 if (old_remote_contact != remote_contact) {
985 /* The remote-contact associated with the chat changed, we need
986 * to keep track of any change of that contact and update the
987 * window each time. */
988 if (remote_contact) {
989 g_signal_connect_swapped (remote_contact, "notify",
990 G_CALLBACK (chat_window_update_chat_tab),
993 if (old_remote_contact) {
994 g_signal_handlers_disconnect_by_func (old_remote_contact,
995 chat_window_update_chat_tab,
999 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1000 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1003 chat_window_update_chat_tab (chat);
1005 window = chat_window_find_chat (chat);
1006 if (window != NULL) {
1007 chat_window_update (window, FALSE);
1012 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1013 EmpathySmiley *smiley,
1016 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1018 GtkTextBuffer *buffer;
1021 chat = priv->current_chat;
1023 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1024 gtk_text_buffer_get_end_iter (buffer, &iter);
1025 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1029 chat_window_conv_activate_cb (GtkAction *action,
1030 EmpathyChatWindow *window)
1032 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1035 EmpathyContact *remote_contact = NULL;
1037 /* Favorite room menu */
1038 is_room = empathy_chat_is_room (priv->current_chat);
1042 gboolean found = FALSE;
1043 EmpathyChatroom *chatroom;
1045 room = empathy_chat_get_id (priv->current_chat);
1046 account = empathy_chat_get_account (priv->current_chat);
1047 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1049 if (chatroom != NULL)
1050 found = empathy_chatroom_is_favorite (chatroom);
1052 DEBUG ("This room %s favorite", found ? "is" : "is not");
1053 gtk_toggle_action_set_active (
1054 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1056 if (chatroom != NULL)
1057 found = empathy_chatroom_is_always_urgent (chatroom);
1059 gtk_toggle_action_set_active (
1060 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1063 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1064 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1066 /* Show contacts menu */
1067 g_object_get (priv->current_chat,
1068 "remote-contact", &remote_contact,
1069 "show-contacts", &active,
1071 if (remote_contact == NULL) {
1072 gtk_toggle_action_set_active (
1073 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1076 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1077 (remote_contact == NULL));
1078 if (remote_contact != NULL) {
1079 g_object_unref (remote_contact);
1084 chat_window_clear_activate_cb (GtkAction *action,
1085 EmpathyChatWindow *window)
1087 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1089 empathy_chat_clear (priv->current_chat);
1093 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1094 EmpathyChatWindow *window)
1096 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1101 EmpathyChatroom *chatroom;
1103 active = gtk_toggle_action_get_active (toggle_action);
1104 account = empathy_chat_get_account (priv->current_chat);
1105 room = empathy_chat_get_id (priv->current_chat);
1106 name = empathy_chat_dup_name (priv->current_chat);
1108 chatroom = empathy_chatroom_manager_ensure_chatroom (
1109 priv->chatroom_manager,
1114 empathy_chatroom_set_favorite (chatroom, active);
1115 g_object_unref (chatroom);
1120 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1121 EmpathyChatWindow *window)
1123 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1128 EmpathyChatroom *chatroom;
1130 active = gtk_toggle_action_get_active (toggle_action);
1131 account = empathy_chat_get_account (priv->current_chat);
1132 room = empathy_chat_get_id (priv->current_chat);
1133 name = empathy_chat_dup_name (priv->current_chat);
1135 chatroom = empathy_chatroom_manager_ensure_chatroom (
1136 priv->chatroom_manager,
1141 empathy_chatroom_set_always_urgent (chatroom, active);
1142 g_object_unref (chatroom);
1147 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1148 EmpathyChatWindow *window)
1150 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1153 active = gtk_toggle_action_get_active (toggle_action);
1155 empathy_chat_set_show_contacts (priv->current_chat, active);
1159 chat_window_invite_participant_activate_cb (GtkAction *action,
1160 EmpathyChatWindow *window)
1162 EmpathyChatWindowPriv *priv;
1164 EmpathyTpChat *tp_chat;
1167 priv = GET_PRIV (window);
1169 g_return_if_fail (priv->current_chat != NULL);
1171 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1173 dialog = empathy_invite_participant_dialog_new (
1174 GTK_WINDOW (priv->dialog), tp_chat);
1175 gtk_widget_show (dialog);
1177 response = gtk_dialog_run (GTK_DIALOG (dialog));
1179 if (response == GTK_RESPONSE_ACCEPT) {
1180 TpContact *tp_contact;
1181 EmpathyContact *contact;
1183 tp_contact = empathy_invite_participant_dialog_get_selected (
1184 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1185 if (tp_contact == NULL) goto out;
1187 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1189 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1191 g_object_unref (contact);
1195 gtk_widget_destroy (dialog);
1199 chat_window_close_activate_cb (GtkAction *action,
1200 EmpathyChatWindow *window)
1202 EmpathyChatWindowPriv *priv;
1204 priv = GET_PRIV (window);
1206 g_return_if_fail (priv->current_chat != NULL);
1208 maybe_close_chat (window, priv->current_chat);
1212 chat_window_edit_activate_cb (GtkAction *action,
1213 EmpathyChatWindow *window)
1215 EmpathyChatWindowPriv *priv;
1216 GtkClipboard *clipboard;
1217 GtkTextBuffer *buffer;
1218 gboolean text_available;
1220 priv = GET_PRIV (window);
1222 g_return_if_fail (priv->current_chat != NULL);
1224 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1225 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1226 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1227 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1231 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1232 if (gtk_text_buffer_get_has_selection (buffer)) {
1233 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1234 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1238 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1240 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1241 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1244 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1245 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1246 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1250 chat_window_cut_activate_cb (GtkAction *action,
1251 EmpathyChatWindow *window)
1253 EmpathyChatWindowPriv *priv;
1255 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1257 priv = GET_PRIV (window);
1259 empathy_chat_cut (priv->current_chat);
1263 chat_window_copy_activate_cb (GtkAction *action,
1264 EmpathyChatWindow *window)
1266 EmpathyChatWindowPriv *priv;
1268 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1270 priv = GET_PRIV (window);
1272 empathy_chat_copy (priv->current_chat);
1276 chat_window_paste_activate_cb (GtkAction *action,
1277 EmpathyChatWindow *window)
1279 EmpathyChatWindowPriv *priv;
1281 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1283 priv = GET_PRIV (window);
1285 empathy_chat_paste (priv->current_chat);
1289 chat_window_find_activate_cb (GtkAction *action,
1290 EmpathyChatWindow *window)
1292 EmpathyChatWindowPriv *priv;
1294 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1296 priv = GET_PRIV (window);
1298 empathy_chat_find (priv->current_chat);
1302 chat_window_tabs_next_activate_cb (GtkAction *action,
1303 EmpathyChatWindow *window)
1305 EmpathyChatWindowPriv *priv;
1306 gint index_, numPages;
1307 gboolean wrap_around;
1309 priv = GET_PRIV (window);
1311 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1312 &wrap_around, NULL);
1314 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1315 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1317 if (index_ == (numPages - 1) && wrap_around) {
1318 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1322 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1326 chat_window_tabs_previous_activate_cb (GtkAction *action,
1327 EmpathyChatWindow *window)
1329 EmpathyChatWindowPriv *priv;
1330 gint index_, numPages;
1331 gboolean wrap_around;
1333 priv = GET_PRIV (window);
1335 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1336 &wrap_around, NULL);
1338 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1339 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1341 if (index_ <= 0 && wrap_around) {
1342 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1346 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1350 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1351 EmpathyChatWindow *window)
1353 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1354 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1355 empathy_get_current_action_time ());
1359 chat_window_tabs_left_activate_cb (GtkAction *action,
1360 EmpathyChatWindow *window)
1362 EmpathyChatWindowPriv *priv;
1364 gint index_, num_pages;
1366 priv = GET_PRIV (window);
1368 chat = priv->current_chat;
1369 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1374 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1378 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1379 chat_window_menu_context_update (priv, num_pages);
1383 chat_window_tabs_right_activate_cb (GtkAction *action,
1384 EmpathyChatWindow *window)
1386 EmpathyChatWindowPriv *priv;
1388 gint index_, num_pages;
1390 priv = GET_PRIV (window);
1392 chat = priv->current_chat;
1393 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1395 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1399 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1400 chat_window_menu_context_update (priv, num_pages);
1403 static EmpathyChatWindow *
1404 empathy_chat_window_new (void)
1406 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1410 chat_window_detach_activate_cb (GtkAction *action,
1411 EmpathyChatWindow *window)
1413 EmpathyChatWindowPriv *priv;
1414 EmpathyChatWindow *new_window;
1417 priv = GET_PRIV (window);
1419 chat = priv->current_chat;
1420 new_window = empathy_chat_window_new ();
1422 empathy_chat_window_move_chat (window, new_window, chat);
1424 priv = GET_PRIV (new_window);
1425 gtk_widget_show (priv->dialog);
1429 chat_window_help_contents_activate_cb (GtkAction *action,
1430 EmpathyChatWindow *window)
1432 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1434 empathy_url_show (priv->dialog, "help:empathy");
1438 chat_window_help_about_activate_cb (GtkAction *action,
1439 EmpathyChatWindow *window)
1441 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1443 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1447 chat_window_delete_event_cb (GtkWidget *dialog,
1449 EmpathyChatWindow *window)
1451 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1452 EmpathyChat *chat = NULL;
1456 DEBUG ("Delete event received");
1458 for (l = priv->chats; l != NULL; l = l->next) {
1459 if (chat_needs_close_confirmation (l->data)) {
1466 confirm_close (window, TRUE, n_rooms,
1467 (n_rooms == 1 ? chat : NULL));
1469 remove_all_chats (window);
1476 chat_window_composing_cb (EmpathyChat *chat,
1477 gboolean is_composing,
1478 EmpathyChatWindow *window)
1480 chat_window_update_chat_tab (chat);
1484 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1487 EmpathyChatWindowPriv *priv;
1489 priv = GET_PRIV (window);
1491 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1495 chat_window_notification_closed_cb (NotifyNotification *notify,
1496 EmpathyChatWindow *self)
1498 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1500 g_object_unref (notify);
1501 if (priv->notification == notify) {
1502 priv->notification = NULL;
1507 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1508 EmpathyMessage *message,
1511 EmpathyContact *sender;
1512 const gchar *header;
1516 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1517 gboolean res, has_x_canonical_append;
1518 NotifyNotification *notification = priv->notification;
1520 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1523 res = g_settings_get_boolean (priv->gsettings_notif,
1524 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1531 sender = empathy_message_get_sender (message);
1532 header = empathy_contact_get_alias (sender);
1533 body = empathy_message_get_body (message);
1534 escaped = g_markup_escape_text (body, -1);
1535 has_x_canonical_append = empathy_notify_manager_has_capability (
1536 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1538 if (notification != NULL && !has_x_canonical_append) {
1539 /* if the notification server supports x-canonical-append, it is
1540 better to not use notify_notification_update to avoid
1541 overwriting the current notification message */
1542 notify_notification_update (notification,
1543 header, escaped, NULL);
1545 /* if the notification server supports x-canonical-append,
1546 the hint will be added, so that the message from the
1547 just created notification will be automatically appended
1548 to an existing notification with the same title.
1549 In this way the previous message will not be lost: the new
1550 message will appear below it, in the same notification */
1551 notification = notify_notification_new (header, escaped, NULL);
1553 if (priv->notification == NULL) {
1554 priv->notification = notification;
1557 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1559 tp_g_signal_connect_object (notification, "closed",
1560 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1562 if (has_x_canonical_append) {
1563 /* We have to set a not empty string to keep libnotify happy */
1564 notify_notification_set_hint_string (notification,
1565 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1569 const gchar *category = empathy_chat_is_room (chat)
1570 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1571 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1572 notify_notification_set_hint (notification,
1573 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1574 g_variant_new_string (category));
1578 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1579 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1581 if (pixbuf != NULL) {
1582 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1583 g_object_unref (pixbuf);
1586 notify_notification_show (notification, NULL);
1592 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1594 EmpathyChatWindowPriv *priv;
1597 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1599 priv = GET_PRIV (window);
1601 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1607 chat_window_new_message_cb (EmpathyChat *chat,
1608 EmpathyMessage *message,
1610 gboolean should_highlight,
1611 EmpathyChatWindow *window)
1613 EmpathyChatWindowPriv *priv;
1615 gboolean needs_urgency;
1616 EmpathyContact *sender;
1618 priv = GET_PRIV (window);
1620 has_focus = empathy_chat_window_has_focus (window);
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)) {
1632 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1633 EMPATHY_SOUND_MESSAGE_OUTGOING);
1636 if (has_focus && priv->current_chat == chat) {
1637 /* window and tab are focused so consider the message to be read */
1639 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1640 empathy_chat_messages_read (chat);
1644 /* Update the chat tab if this is the first unread message */
1645 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1646 chat_window_update_chat_tab (chat);
1649 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1650 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1651 * an unamed MUC (msn-like).
1652 * In case of a MUC, we set urgency if either:
1653 * a) the chatroom's always_urgent property is TRUE
1654 * b) the message contains our alias
1656 if (empathy_chat_is_room (chat)) {
1659 EmpathyChatroom *chatroom;
1661 account = empathy_chat_get_account (chat);
1662 room = empathy_chat_get_id (chat);
1664 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1667 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1668 needs_urgency = TRUE;
1670 needs_urgency = should_highlight;
1673 needs_urgency = TRUE;
1676 if (needs_urgency) {
1678 chat_window_set_urgency_hint (window, TRUE);
1681 /* Pending messages have already been displayed and notified in the
1682 * approver, so we don't display a notification and play a sound for those */
1684 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1685 EMPATHY_SOUND_MESSAGE_INCOMING);
1687 chat_window_show_or_update_notification (window, message, chat);
1691 /* update the number of unread messages and the window icon */
1692 chat_window_title_update (priv);
1693 chat_window_icon_update (priv, TRUE);
1697 chat_window_command_part (EmpathyChat *chat,
1700 EmpathyChat *chat_to_be_parted;
1701 EmpathyTpChat *tp_chat = NULL;
1703 if (strv[1] == NULL) {
1704 /* No chatroom ID specified */
1705 tp_chat = empathy_chat_get_tp_chat (chat);
1707 empathy_tp_chat_leave (tp_chat, "");
1710 chat_to_be_parted = empathy_chat_window_find_chat (
1711 empathy_chat_get_account (chat), strv[1], FALSE);
1713 if (chat_to_be_parted != NULL) {
1714 /* Found a chatroom matching the specified ID */
1715 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1717 empathy_tp_chat_leave (tp_chat, strv[2]);
1721 /* Going by the syntax of PART command:
1723 * /PART [<chatroom-ID>] [<reason>]
1725 * Chatroom-ID is not a must to specify a reason.
1726 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1727 * MUC then the current chatroom should be parted and srtv[1] should
1728 * be treated as part of the optional part-message. */
1729 message = g_strconcat (strv[1], " ", strv[2], NULL);
1730 tp_chat = empathy_chat_get_tp_chat (chat);
1732 empathy_tp_chat_leave (tp_chat, message);
1738 static GtkNotebook *
1739 notebook_create_window_cb (GtkNotebook *source,
1745 EmpathyChatWindowPriv *priv;
1746 EmpathyChatWindow *window, *new_window;
1749 chat = EMPATHY_CHAT (page);
1750 window = chat_window_find_chat (chat);
1752 new_window = empathy_chat_window_new ();
1753 priv = GET_PRIV (new_window);
1755 DEBUG ("Detach hook called");
1757 empathy_chat_window_move_chat (window, new_window, chat);
1759 gtk_widget_show (priv->dialog);
1760 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1766 chat_window_page_switched_cb (GtkNotebook *notebook,
1769 EmpathyChatWindow *window)
1771 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1772 EmpathyChat *chat = EMPATHY_CHAT (child);
1774 DEBUG ("Page switched");
1776 if (priv->page_added) {
1777 priv->page_added = FALSE;
1778 empathy_chat_scroll_down (chat);
1780 else if (priv->current_chat == chat) {
1784 priv->current_chat = chat;
1785 empathy_chat_messages_read (chat);
1787 chat_window_update_chat_tab (chat);
1791 chat_window_page_added_cb (GtkNotebook *notebook,
1794 EmpathyChatWindow *window)
1796 EmpathyChatWindowPriv *priv;
1799 priv = GET_PRIV (window);
1801 /* If we just received DND to the same window, we don't want
1802 * to do anything here like removing the tab and then readding
1803 * it, so we return here and in "page-added".
1805 if (priv->dnd_same_window) {
1806 DEBUG ("Page added (back to the same window)");
1807 priv->dnd_same_window = FALSE;
1811 DEBUG ("Page added");
1813 /* Get chat object */
1814 chat = EMPATHY_CHAT (child);
1816 /* Connect chat signals for this window */
1817 g_signal_connect (chat, "composing",
1818 G_CALLBACK (chat_window_composing_cb),
1820 g_signal_connect (chat, "new-message",
1821 G_CALLBACK (chat_window_new_message_cb),
1823 g_signal_connect (chat, "part-command-entered",
1824 G_CALLBACK (chat_window_command_part),
1826 g_signal_connect (chat, "notify::tp-chat",
1827 G_CALLBACK (chat_window_update_chat_tab),
1830 /* Set flag so we know to perform some special operations on
1831 * switch page due to the new page being added.
1833 priv->page_added = TRUE;
1835 /* Get list of chats up to date */
1836 priv->chats = g_list_append (priv->chats, chat);
1838 chat_window_update_chat_tab (chat);
1842 chat_window_page_removed_cb (GtkNotebook *notebook,
1845 EmpathyChatWindow *window)
1847 EmpathyChatWindowPriv *priv;
1850 priv = GET_PRIV (window);
1852 /* If we just received DND to the same window, we don't want
1853 * to do anything here like removing the tab and then readding
1854 * it, so we return here and in "page-added".
1856 if (priv->dnd_same_window) {
1857 DEBUG ("Page removed (and will be readded to same window)");
1861 DEBUG ("Page removed");
1863 /* Get chat object */
1864 chat = EMPATHY_CHAT (child);
1866 /* Disconnect all signal handlers for this chat and this window */
1867 g_signal_handlers_disconnect_by_func (chat,
1868 G_CALLBACK (chat_window_composing_cb),
1870 g_signal_handlers_disconnect_by_func (chat,
1871 G_CALLBACK (chat_window_new_message_cb),
1873 g_signal_handlers_disconnect_by_func (chat,
1874 G_CALLBACK (chat_window_update_chat_tab),
1877 /* Keep list of chats up to date */
1878 priv->chats = g_list_remove (priv->chats, chat);
1879 empathy_chat_messages_read (chat);
1881 if (priv->chats == NULL) {
1882 g_object_unref (window);
1884 chat_window_update (window, TRUE);
1889 chat_window_focus_in_event_cb (GtkWidget *widget,
1891 EmpathyChatWindow *window)
1893 EmpathyChatWindowPriv *priv;
1895 priv = GET_PRIV (window);
1897 empathy_chat_messages_read (priv->current_chat);
1899 chat_window_set_urgency_hint (window, FALSE);
1901 /* Update the title, since we now mark all unread messages as read. */
1902 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1908 chat_window_drag_drop (GtkWidget *widget,
1909 GdkDragContext *context,
1913 EmpathyChatWindow *window)
1916 EmpathyChatWindowPriv *priv;
1918 priv = GET_PRIV (window);
1920 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1921 if (target == GDK_NONE)
1922 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1924 if (target != GDK_NONE) {
1925 gtk_drag_get_data (widget, context, target, time_);
1933 chat_window_drag_motion (GtkWidget *widget,
1934 GdkDragContext *context,
1938 EmpathyChatWindow *window)
1941 EmpathyChatWindowPriv *priv;
1943 priv = GET_PRIV (window);
1945 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1946 if (target != GDK_NONE) {
1947 /* This is a file drag. Ensure the contact is online and set the
1948 drag type to COPY. Note that it's possible that the tab will
1949 be switched by GTK+ after a timeout from drag_motion without
1950 getting another drag_motion to disable the drop. You have
1951 to hold your mouse really still.
1953 EmpathyContact *contact;
1955 priv = GET_PRIV (window);
1956 contact = empathy_chat_get_remote_contact (priv->current_chat);
1957 /* contact is NULL for multi-user chats. We don't do
1958 * file transfers to MUCs. We also don't send files
1959 * to offline contacts or contacts that don't support
1962 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1963 gdk_drag_status (context, 0, time_);
1966 if (!(empathy_contact_get_capabilities (contact)
1967 & EMPATHY_CAPABILITIES_FT)) {
1968 gdk_drag_status (context, 0, time_);
1971 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1975 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1976 if (target != GDK_NONE) {
1977 /* This is a drag of a contact from a contact list. Set to COPY.
1978 FIXME: If this drag is to a MUC window, it invites the user.
1979 Otherwise, it opens a chat. Should we use a different drag
1980 type for invites? Should we allow ASK?
1982 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1990 drag_data_received_individual_id (EmpathyChatWindow *self,
1992 GdkDragContext *context,
1995 GtkSelectionData *selection,
2000 EmpathyIndividualManager *manager = NULL;
2001 FolksIndividual *individual;
2002 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2003 EmpathyTpChat *chat;
2004 TpContact *tp_contact;
2006 EmpathyContact *contact;
2008 id = (const gchar *) gtk_selection_data_get_data (selection);
2010 DEBUG ("DND invididual %s", id);
2012 if (priv->current_chat == NULL)
2015 chat = empathy_chat_get_tp_chat (priv->current_chat);
2019 if (!empathy_tp_chat_can_add_contact (chat)) {
2020 DEBUG ("Can't invite contact to %s",
2021 tp_proxy_get_object_path (chat));
2025 manager = empathy_individual_manager_dup_singleton ();
2027 individual = empathy_individual_manager_lookup_member (manager, id);
2028 if (individual == NULL) {
2029 DEBUG ("Failed to find individual %s", id);
2033 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2034 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2035 if (tp_contact == NULL) {
2036 DEBUG ("Can't find a TpContact on connection %s for %s",
2037 tp_proxy_get_object_path (conn), id);
2041 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2042 tp_channel_get_identifier ((TpChannel *) chat));
2044 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2045 empathy_tp_chat_add (chat, contact, NULL);
2046 g_object_unref (contact);
2049 gtk_drag_finish (context, TRUE, FALSE, time_);
2050 tp_clear_object (&manager);
2054 chat_window_drag_data_received (GtkWidget *widget,
2055 GdkDragContext *context,
2058 GtkSelectionData *selection,
2061 EmpathyChatWindow *window)
2063 if (info == DND_DRAG_TYPE_CONTACT_ID) {
2064 EmpathyChat *chat = NULL;
2065 EmpathyChatWindow *old_window;
2066 TpAccount *account = NULL;
2067 EmpathyClientFactory *factory;
2070 const gchar *account_id;
2071 const gchar *contact_id;
2073 id = (const gchar*) gtk_selection_data_get_data (selection);
2075 factory = empathy_client_factory_dup ();
2077 DEBUG ("DND contact from roster with id:'%s'", id);
2079 strv = g_strsplit (id, ":", 2);
2080 if (g_strv_length (strv) == 2) {
2081 account_id = strv[0];
2082 contact_id = strv[1];
2084 tp_simple_client_factory_ensure_account (
2085 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2088 g_object_unref (factory);
2089 if (account != NULL)
2090 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2093 if (account == NULL) {
2095 gtk_drag_finish (context, FALSE, FALSE, time_);
2100 empathy_chat_with_contact_id (
2101 account, contact_id,
2102 empathy_get_current_action_time (),
2110 old_window = chat_window_find_chat (chat);
2112 if (old_window == window) {
2113 gtk_drag_finish (context, TRUE, FALSE, time_);
2117 empathy_chat_window_move_chat (old_window, window, chat);
2119 empathy_chat_window_add_chat (window, chat);
2122 /* Added to take care of any outstanding chat events */
2123 empathy_chat_window_present_chat (chat,
2124 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2126 /* We should return TRUE to remove the data when doing
2127 * GDK_ACTION_MOVE, but we don't here otherwise it has
2128 * weird consequences, and we handle that internally
2129 * anyway with add_chat () and remove_chat ().
2131 gtk_drag_finish (context, TRUE, FALSE, time_);
2133 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2134 drag_data_received_individual_id (window, widget, context, x, y,
2135 selection, info, time_);
2137 else if (info == DND_DRAG_TYPE_URI_LIST) {
2138 EmpathyChatWindowPriv *priv;
2139 EmpathyContact *contact;
2142 priv = GET_PRIV (window);
2143 contact = empathy_chat_get_remote_contact (priv->current_chat);
2145 /* contact is NULL when current_chat is a multi-user chat.
2146 * We don't do file transfers to MUCs, so just cancel the drag.
2148 if (contact == NULL) {
2149 gtk_drag_finish (context, TRUE, FALSE, time_);
2153 data = (const gchar *) gtk_selection_data_get_data (selection);
2154 empathy_send_file_from_uri_list (contact, data);
2156 gtk_drag_finish (context, TRUE, FALSE, time_);
2158 else if (info == DND_DRAG_TYPE_TAB) {
2160 EmpathyChatWindow *old_window = NULL;
2164 chat = (void *) gtk_selection_data_get_data (selection);
2165 old_window = chat_window_find_chat (*chat);
2168 EmpathyChatWindowPriv *priv;
2170 priv = GET_PRIV (window);
2171 priv->dnd_same_window = (old_window == window);
2172 DEBUG ("DND tab (within same window: %s)",
2173 priv->dnd_same_window ? "Yes" : "No");
2176 DEBUG ("DND from unknown source");
2177 gtk_drag_finish (context, FALSE, FALSE, time_);
2182 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2183 guint num_chats_in_manager,
2184 EmpathyChatWindow *window)
2186 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2188 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2189 num_chats_in_manager > 0);
2193 chat_window_finalize (GObject *object)
2195 EmpathyChatWindow *window;
2196 EmpathyChatWindowPriv *priv;
2198 window = EMPATHY_CHAT_WINDOW (object);
2199 priv = GET_PRIV (window);
2201 DEBUG ("Finalized: %p", object);
2203 g_object_unref (priv->ui_manager);
2204 g_object_unref (priv->chatroom_manager);
2205 g_object_unref (priv->notify_mgr);
2206 g_object_unref (priv->gsettings_chat);
2207 g_object_unref (priv->gsettings_notif);
2208 g_object_unref (priv->gsettings_ui);
2209 g_object_unref (priv->sound_mgr);
2211 if (priv->notification != NULL) {
2212 notify_notification_close (priv->notification, NULL);
2213 priv->notification = NULL;
2216 if (priv->contact_targets) {
2217 gtk_target_list_unref (priv->contact_targets);
2219 if (priv->file_targets) {
2220 gtk_target_list_unref (priv->file_targets);
2223 if (priv->chat_manager) {
2224 g_signal_handler_disconnect (priv->chat_manager,
2225 priv->chat_manager_chats_changed_id);
2226 g_object_unref (priv->chat_manager);
2227 priv->chat_manager = NULL;
2230 chat_windows = g_list_remove (chat_windows, window);
2231 gtk_widget_destroy (priv->dialog);
2233 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2237 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2239 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2241 object_class->finalize = chat_window_finalize;
2243 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2247 empathy_chat_window_init (EmpathyChatWindow *window)
2250 GtkAccelGroup *accel_group;
2255 GtkWidget *chat_vbox;
2257 EmpathySmileyManager *smiley_manager;
2258 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2259 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2261 window->priv = priv;
2262 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2263 gui = empathy_builder_get_file (filename,
2264 "chat_window", &priv->dialog,
2265 "chat_vbox", &chat_vbox,
2266 "ui_manager", &priv->ui_manager,
2267 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2268 "menu_conv_favorite", &priv->menu_conv_favorite,
2269 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2270 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2271 "menu_edit_cut", &priv->menu_edit_cut,
2272 "menu_edit_copy", &priv->menu_edit_copy,
2273 "menu_edit_paste", &priv->menu_edit_paste,
2274 "menu_edit_find", &priv->menu_edit_find,
2275 "menu_tabs_next", &priv->menu_tabs_next,
2276 "menu_tabs_prev", &priv->menu_tabs_prev,
2277 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2278 "menu_tabs_left", &priv->menu_tabs_left,
2279 "menu_tabs_right", &priv->menu_tabs_right,
2280 "menu_tabs_detach", &priv->menu_tabs_detach,
2284 empathy_builder_connect (gui, window,
2285 "menu_conv", "activate", chat_window_conv_activate_cb,
2286 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2287 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2288 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2289 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2290 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2291 "menu_conv_close", "activate", chat_window_close_activate_cb,
2292 "menu_edit", "activate", chat_window_edit_activate_cb,
2293 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2294 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2295 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2296 "menu_edit_find", "activate", chat_window_find_activate_cb,
2297 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2298 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2299 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2300 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2301 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2302 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2303 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2304 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2307 g_object_ref (priv->ui_manager);
2308 g_object_unref (gui);
2310 empathy_set_rss_provider (GTK_WIDGET (priv->dialog));
2312 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2313 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2314 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2315 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2317 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2319 priv->notebook = gtk_notebook_new ();
2321 g_signal_connect (priv->notebook, "create-window",
2322 G_CALLBACK (notebook_create_window_cb), window);
2324 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2325 "EmpathyChatWindow");
2326 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2327 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2328 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2329 gtk_widget_show (priv->notebook);
2332 accel_group = gtk_accel_group_new ();
2333 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2335 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2336 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2339 gtk_accel_group_connect (accel_group,
2346 g_object_unref (accel_group);
2348 /* Set up drag target lists */
2349 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2350 G_N_ELEMENTS (drag_types_dest_contact));
2351 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2352 G_N_ELEMENTS (drag_types_dest_file));
2354 /* Set up smiley menu */
2355 smiley_manager = empathy_smiley_manager_dup_singleton ();
2356 submenu = empathy_smiley_menu_new (smiley_manager,
2357 chat_window_insert_smiley_activate_cb,
2359 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2360 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2361 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2362 g_object_unref (smiley_manager);
2364 /* Set up signals we can't do with ui file since we may need to
2365 * block/unblock them at some later stage.
2368 g_signal_connect (priv->dialog,
2370 G_CALLBACK (chat_window_delete_event_cb),
2372 g_signal_connect (priv->dialog,
2374 G_CALLBACK (chat_window_focus_in_event_cb),
2376 g_signal_connect_after (priv->notebook,
2378 G_CALLBACK (chat_window_page_switched_cb),
2380 g_signal_connect (priv->notebook,
2382 G_CALLBACK (chat_window_page_added_cb),
2384 g_signal_connect (priv->notebook,
2386 G_CALLBACK (chat_window_page_removed_cb),
2389 /* Set up drag and drop */
2390 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2391 GTK_DEST_DEFAULT_HIGHLIGHT,
2393 G_N_ELEMENTS (drag_types_dest),
2394 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2396 /* connect_after to allow GtkNotebook's built-in tab switching */
2397 g_signal_connect_after (priv->notebook,
2399 G_CALLBACK (chat_window_drag_motion),
2401 g_signal_connect (priv->notebook,
2402 "drag-data-received",
2403 G_CALLBACK (chat_window_drag_data_received),
2405 g_signal_connect (priv->notebook,
2407 G_CALLBACK (chat_window_drag_drop),
2410 chat_windows = g_list_prepend (chat_windows, window);
2412 /* Set up private details */
2414 priv->current_chat = NULL;
2415 priv->notification = NULL;
2417 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2419 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2420 priv->chat_manager_chats_changed_id =
2421 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2422 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2425 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2426 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2430 /* Returns the window to open a new tab in if there is a suitable window,
2431 * otherwise, returns NULL indicating that a new window should be added.
2433 static EmpathyChatWindow *
2434 empathy_chat_window_get_default (gboolean room)
2436 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2438 gboolean separate_windows = TRUE;
2440 separate_windows = g_settings_get_boolean (gsettings,
2441 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2443 g_object_unref (gsettings);
2445 if (separate_windows) {
2446 /* Always create a new window */
2450 for (l = chat_windows; l; l = l->next) {
2451 EmpathyChatWindow *chat_window;
2452 guint nb_rooms, nb_private;
2454 chat_window = l->data;
2456 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2458 /* Skip the window if there aren't any rooms in it */
2459 if (room && nb_rooms == 0)
2462 /* Skip the window if there aren't any 1-1 chats in it */
2463 if (!room && nb_private == 0)
2473 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2476 EmpathyChatWindowPriv *priv;
2478 GtkWidget *popup_label;
2480 GValue value = { 0, };
2482 g_return_if_fail (window != NULL);
2483 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2485 priv = GET_PRIV (window);
2487 /* Reference the chat object */
2488 g_object_ref (chat);
2490 /* If this window has just been created, position it */
2491 if (priv->chats == NULL) {
2492 const gchar *name = "chat-window";
2493 gboolean separate_windows;
2495 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2496 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2498 if (empathy_chat_is_room (chat))
2499 name = "room-window";
2501 if (separate_windows) {
2504 /* Save current position of the window */
2505 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2507 /* First bind to the 'generic' name. So new window for which we didn't
2508 * save a geometry yet will have the geometry of the last saved
2509 * window (bgo #601191). */
2510 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2512 /* Restore previous position of the window so the newly created window
2513 * won't be in the same position as the latest saved window and so
2514 * completely hide it. */
2515 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2517 /* Then bind it to the name of the contact/room so we'll save the
2518 * geometry specific to this window */
2519 name = empathy_chat_get_id (chat);
2522 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2525 child = GTK_WIDGET (chat);
2526 label = chat_window_create_label (window, chat, TRUE);
2527 popup_label = chat_window_create_label (window, chat, FALSE);
2528 gtk_widget_show (child);
2530 g_signal_connect (chat, "notify::name",
2531 G_CALLBACK (chat_window_chat_notify_cb),
2533 g_signal_connect (chat, "notify::subject",
2534 G_CALLBACK (chat_window_chat_notify_cb),
2536 g_signal_connect (chat, "notify::remote-contact",
2537 G_CALLBACK (chat_window_chat_notify_cb),
2539 g_signal_connect (chat, "notify::sms-channel",
2540 G_CALLBACK (chat_window_chat_notify_cb),
2542 g_signal_connect (chat, "notify::n-messages-sending",
2543 G_CALLBACK (chat_window_chat_notify_cb),
2545 g_signal_connect (chat, "notify::nb-unread-messages",
2546 G_CALLBACK (chat_window_chat_notify_cb),
2548 chat_window_chat_notify_cb (chat);
2550 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2551 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2552 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2553 g_value_init (&value, G_TYPE_BOOLEAN);
2554 g_value_set_boolean (&value, TRUE);
2555 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2556 child, "tab-expand" , &value);
2557 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2558 child, "tab-fill" , &value);
2559 g_value_unset (&value);
2561 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2565 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2568 EmpathyChatWindowPriv *priv;
2570 EmpathyContact *remote_contact;
2571 EmpathyChatManager *chat_manager;
2573 g_return_if_fail (window != NULL);
2574 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2576 priv = GET_PRIV (window);
2578 g_signal_handlers_disconnect_by_func (chat,
2579 chat_window_chat_notify_cb,
2581 remote_contact = g_object_get_data (G_OBJECT (chat),
2582 "chat-window-remote-contact");
2583 if (remote_contact) {
2584 g_signal_handlers_disconnect_by_func (remote_contact,
2585 chat_window_update_chat_tab,
2589 chat_manager = empathy_chat_manager_dup_singleton ();
2590 empathy_chat_manager_closed_chat (chat_manager, chat);
2591 g_object_unref (chat_manager);
2593 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2595 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2597 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2599 g_object_unref (chat);
2603 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2604 EmpathyChatWindow *new_window,
2609 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2610 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2611 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2613 widget = GTK_WIDGET (chat);
2615 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2616 G_OBJECT (widget)->ref_count);
2618 /* We reference here to make sure we don't loose the widget
2619 * and the EmpathyChat object during the move.
2621 g_object_ref (chat);
2622 g_object_ref (widget);
2624 empathy_chat_window_remove_chat (old_window, chat);
2625 empathy_chat_window_add_chat (new_window, chat);
2627 g_object_unref (widget);
2628 g_object_unref (chat);
2632 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2635 EmpathyChatWindowPriv *priv;
2638 g_return_if_fail (window != NULL);
2639 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2641 priv = GET_PRIV (window);
2643 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2645 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2650 empathy_chat_window_find_chat (TpAccount *account,
2652 gboolean sms_channel)
2656 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2658 for (l = chat_windows; l; l = l->next) {
2659 EmpathyChatWindowPriv *priv;
2660 EmpathyChatWindow *window;
2664 priv = GET_PRIV (window);
2666 for (ll = priv->chats; ll; ll = ll->next) {
2671 if (account == empathy_chat_get_account (chat) &&
2672 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2673 sms_channel == empathy_chat_is_sms_channel (chat)) {
2683 empathy_chat_window_present_chat (EmpathyChat *chat,
2686 EmpathyChatWindow *window;
2687 EmpathyChatWindowPriv *priv;
2688 guint32 x_timestamp;
2690 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2692 window = chat_window_find_chat (chat);
2694 /* If the chat has no window, create one */
2695 if (window == NULL) {
2696 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2698 window = empathy_chat_window_new ();
2700 /* we want to display the newly created window even if we don't present
2702 priv = GET_PRIV (window);
2703 gtk_widget_show (priv->dialog);
2706 empathy_chat_window_add_chat (window, chat);
2709 /* Don't force the window to show itself when it wasn't
2710 * an action by the user
2712 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2715 priv = GET_PRIV (window);
2717 if (x_timestamp != GDK_CURRENT_TIME) {
2718 /* Don't present or switch tab if the action was earlier than the
2719 * last actions X time, accounting for overflow and the first ever
2722 if (priv->x_user_action_time != 0
2723 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2726 priv->x_user_action_time = x_timestamp;
2729 empathy_chat_window_switch_to_chat (window, chat);
2731 /* Don't use empathy_window_present_with_time () which would move the window
2732 * to our current desktop but move to the window's desktop instead. This is
2733 * more coherent with Shell's 'app is ready' notication which moves the view
2734 * to the app desktop rather than moving the app itself. */
2735 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2737 gtk_widget_grab_focus (chat->input_text_view);
2741 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2745 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2747 guint _nb_rooms = 0, _nb_private = 0;
2749 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2750 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2756 if (nb_rooms != NULL)
2757 *nb_rooms = _nb_rooms;
2758 if (nb_private != NULL)
2759 *nb_private = _nb_private;