2 * Copyright (C) 2003-2007 Imendio AB
3 * Copyright (C) 2007-2012 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Richard Hult <richard@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24 * Xavier Claessens <xclaesse@gmail.com>
25 * Rômulo Fernandes Machado <romulo@castorgroup.net>
33 #include <gdk/gdkkeysyms.h>
35 #include <glib/gi18n.h>
36 #include <libnotify/notification.h>
38 #include <libempathy/empathy-client-factory.h>
39 #include <libempathy/empathy-contact.h>
40 #include <libempathy/empathy-message.h>
41 #include <libempathy/empathy-chatroom-manager.h>
42 #include <libempathy/empathy-gsettings.h>
43 #include <libempathy/empathy-utils.h>
44 #include <libempathy/empathy-request-util.h>
45 #include <libempathy/empathy-individual-manager.h>
47 #include <libempathy-gtk/empathy-images.h>
48 #include <libempathy-gtk/empathy-log-window.h>
49 #include <libempathy-gtk/empathy-geometry.h>
50 #include <libempathy-gtk/empathy-smiley-manager.h>
51 #include <libempathy-gtk/empathy-sound-manager.h>
52 #include <libempathy-gtk/empathy-ui-utils.h>
53 #include <libempathy-gtk/empathy-notify-manager.h>
55 #include "empathy-chat-manager.h"
56 #include "empathy-chat-window.h"
57 #include "empathy-about-dialog.h"
58 #include "empathy-invite-participant-dialog.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
61 #include <libempathy/empathy-debug.h>
63 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
65 #define X_EARLIER_OR_EQL(t1, t2) \
66 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
67 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
70 struct _EmpathyChatWindowPriv
72 EmpathyChat *current_chat;
75 gboolean dnd_same_window;
76 EmpathyChatroomManager *chatroom_manager;
77 EmpathyNotifyManager *notify_mgr;
78 EmpathyIndividualManager *individual_mgr;
81 NotifyNotification *notification;
83 GtkTargetList *contact_targets;
84 GtkTargetList *file_targets;
86 EmpathyChatManager *chat_manager;
87 gulong chat_manager_chats_changed_id;
90 GtkUIManager *ui_manager;
91 GtkAction *menu_conv_insert_smiley;
92 GtkAction *menu_conv_favorite;
93 GtkAction *menu_conv_always_urgent;
94 GtkAction *menu_conv_toggle_contacts;
96 GtkAction *menu_edit_cut;
97 GtkAction *menu_edit_copy;
98 GtkAction *menu_edit_paste;
99 GtkAction *menu_edit_find;
101 GtkAction *menu_tabs_next;
102 GtkAction *menu_tabs_prev;
103 GtkAction *menu_tabs_undo_close_tab;
104 GtkAction *menu_tabs_left;
105 GtkAction *menu_tabs_right;
106 GtkAction *menu_tabs_detach;
108 /* Last user action time we acted upon to show a tab */
109 guint32 x_user_action_time;
111 GSettings *gsettings_chat;
112 GSettings *gsettings_notif;
113 GSettings *gsettings_ui;
115 EmpathySoundManager *sound_mgr;
117 gboolean updating_menu;
120 static GList *chat_windows = NULL;
122 static const guint tab_accel_keys[] =
124 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
125 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[] =
138 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
139 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
140 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
141 /* FIXME: disabled because of bug #640513
142 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
143 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
147 static const GtkTargetEntry drag_types_dest_contact[] =
149 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
150 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
153 static const GtkTargetEntry drag_types_dest_file[] =
155 /* must be first to be prioritized, in order to receive the
156 * note's file path from Tomboy instead of an URI */
157 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
158 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
161 static void chat_window_update (EmpathyChatWindow *window,
162 gboolean update_contact_menu);
164 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
167 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
170 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
171 EmpathyChatWindow *new_window,
174 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
178 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT)
181 chat_window_accel_cb (GtkAccelGroup *accelgroup,
185 EmpathyChatWindow *self)
190 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
192 if (tab_accel_keys[i] == key)
200 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
203 static EmpathyChatWindow *
204 chat_window_find_chat (EmpathyChat *chat)
208 for (l = chat_windows; l; l = l->next)
210 EmpathyChatWindow *window = l->data;
212 ll = g_list_find (window->priv->chats, chat);
221 remove_all_chats (EmpathyChatWindow *self)
225 while (self->priv->chats)
226 empathy_chat_window_remove_chat (self, self->priv->chats->data);
228 g_object_unref (self);
232 confirm_close_response_cb (GtkWidget *dialog,
234 EmpathyChatWindow *window)
238 chat = g_object_get_data (G_OBJECT (dialog), "chat");
240 gtk_widget_destroy (dialog);
242 if (response != GTK_RESPONSE_ACCEPT)
246 empathy_chat_window_remove_chat (window, chat);
248 remove_all_chats (window);
252 confirm_close (EmpathyChatWindow *self,
253 gboolean close_window,
258 gchar *primary, *secondary;
260 g_return_if_fail (n_rooms > 0);
263 g_return_if_fail (chat == NULL);
265 g_return_if_fail (chat != NULL);
267 /* If there are no chats in this window, how could we possibly have got
270 g_return_if_fail (self->priv->chats != NULL);
272 /* Treat closing a window which only has one tab exactly like closing
275 if (close_window && self->priv->chats->next == NULL)
277 close_window = FALSE;
278 chat = self->priv->chats->data;
283 primary = g_strdup (_("Close this window?"));
287 gchar *chat_name = empathy_chat_dup_name (chat);
288 secondary = g_strdup_printf (
289 _("Closing this window will leave %s. You will "
290 "not receive any further messages until you "
297 secondary = g_strdup_printf (
298 /* Note to translators: the number of chats will
299 * always be at least 2.
302 "Closing this window will leave a chat room. You will "
303 "not receive any further messages until you rejoin it.",
304 "Closing this window will leave %u chat rooms. You will "
305 "not receive any further messages until you rejoin them.",
312 gchar *chat_name = empathy_chat_dup_name (chat);
313 primary = g_strdup_printf (_("Leave %s?"), chat_name);
314 secondary = g_strdup (
315 _("You will not receive any further messages from this chat "
316 "room until you rejoin it."));
320 dialog = gtk_message_dialog_new (
321 GTK_WINDOW (self->priv->dialog),
322 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
327 gtk_window_set_title (GTK_WINDOW (dialog), "");
328 g_object_set (dialog, "secondary-text", secondary, NULL);
333 gtk_dialog_add_button (GTK_DIALOG (dialog),
334 close_window ? _("Close window") : _("Leave room"),
335 GTK_RESPONSE_ACCEPT);
336 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
337 GTK_RESPONSE_ACCEPT);
340 g_object_set_data (G_OBJECT (dialog), "chat", chat);
342 g_signal_connect (dialog, "response",
343 G_CALLBACK (confirm_close_response_cb), self);
345 gtk_window_present (GTK_WINDOW (dialog));
348 /* Returns TRUE if we should check if the user really wants to leave. If it's
349 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
350 * the user is actually in the room as opposed to having been kicked or gone
351 * offline or something), then we should check.
354 chat_needs_close_confirmation (EmpathyChat *chat)
356 return (empathy_chat_is_room (chat) &&
357 empathy_chat_get_tp_chat (chat) != NULL);
361 maybe_close_chat (EmpathyChatWindow *window,
364 g_return_if_fail (chat != NULL);
366 if (chat_needs_close_confirmation (chat))
367 confirm_close (window, FALSE, 1, chat);
369 empathy_chat_window_remove_chat (window, chat);
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 create_close_button (void)
418 GtkWidget *button, *image;
419 GtkStyleContext *context;
421 button = gtk_button_new ();
423 context = gtk_widget_get_style_context (button);
424 gtk_style_context_add_class (context, "empathy-tab-close-button");
426 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
427 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
429 /* We don't want focus/keynav for the button to avoid clutter, and
430 * Ctrl-W works anyway.
432 gtk_widget_set_can_focus (button, FALSE);
433 gtk_widget_set_can_default (button, FALSE);
435 image = gtk_image_new_from_icon_name ("window-close-symbolic",
437 gtk_widget_show (image);
439 gtk_container_add (GTK_CONTAINER (button), image);
445 chat_window_create_label (EmpathyChatWindow *window,
447 gboolean is_tab_label)
450 GtkWidget *name_label;
451 GtkWidget *status_image;
452 GtkWidget *event_box;
453 GtkWidget *event_box_hbox;
454 PangoAttrList *attr_list;
455 PangoAttribute *attr;
457 /* The spacing between the button and the label. */
458 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
460 event_box = gtk_event_box_new ();
461 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
463 name_label = gtk_label_new (NULL);
465 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
467 attr_list = pango_attr_list_new ();
468 attr = pango_attr_scale_new (1/1.2);
469 attr->start_index = 0;
470 attr->end_index = -1;
471 pango_attr_list_insert (attr_list, attr);
472 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
473 pango_attr_list_unref (attr_list);
475 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
476 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
477 g_object_set_data (G_OBJECT (chat),
478 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
481 status_image = gtk_image_new ();
483 /* Spacing between the icon and label. */
484 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
486 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
487 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
489 g_object_set_data (G_OBJECT (chat),
490 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
492 g_object_set_data (G_OBJECT (chat),
493 is_tab_label ? "chat-window-tab-tooltip-widget" :
494 "chat-window-menu-tooltip-widget",
497 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
498 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
502 GtkWidget *close_button;
503 GtkWidget *sending_spinner;
505 sending_spinner = gtk_spinner_new ();
507 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
509 g_object_set_data (G_OBJECT (chat),
510 "chat-window-tab-sending-spinner",
513 close_button = create_close_button ();
514 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
517 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
519 g_signal_connect (close_button,
521 G_CALLBACK (chat_window_close_clicked_cb), chat);
523 /* React to theme changes and also setup the size correctly. */
524 g_signal_connect (hbox, "style-updated",
525 G_CALLBACK (chat_tab_style_updated_cb), chat);
528 gtk_widget_show_all (hbox);
534 _submenu_notify_visible_changed_cb (GObject *object,
538 g_signal_handlers_disconnect_by_func (object,
539 _submenu_notify_visible_changed_cb, userdata);
541 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
545 chat_window_menu_context_update (EmpathyChatWindow *self,
550 gboolean wrap_around;
551 gboolean is_connected;
554 page_num = gtk_notebook_get_current_page (
555 GTK_NOTEBOOK (self->priv->notebook));
556 first_page = (page_num == 0);
557 last_page = (page_num == (num_pages - 1));
558 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
560 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
562 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
564 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
566 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
567 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
568 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
569 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
573 chat_window_conversation_menu_update (EmpathyChatWindow *self)
575 EmpathyTpChat *tp_chat;
576 TpConnection *connection;
578 gboolean sensitive = FALSE;
580 g_return_if_fail (self->priv->current_chat != NULL);
582 action = gtk_ui_manager_get_action (self->priv->ui_manager,
583 "/chats_menubar/menu_conv/menu_conv_invite_participant");
584 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
588 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
590 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
591 (tp_connection_get_status (connection, NULL) ==
592 TP_CONNECTION_STATUS_CONNECTED);
595 gtk_action_set_sensitive (action, sensitive);
599 chat_window_contact_menu_update (EmpathyChatWindow *self)
601 GtkWidget *menu, *submenu, *orig_submenu;
603 if (self->priv->updating_menu)
605 self->priv->updating_menu = TRUE;
607 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
608 "/chats_menubar/menu_contact");
609 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
611 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
613 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
617 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
618 g_object_set_data (G_OBJECT (submenu), "window", self->priv->dialog);
620 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
621 gtk_widget_show (menu);
622 gtk_widget_set_sensitive (menu, TRUE);
626 gtk_widget_set_sensitive (menu, FALSE);
631 tp_g_signal_connect_object (orig_submenu,
633 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
636 self->priv->updating_menu = FALSE;
640 get_all_unread_messages (EmpathyChatWindow *self)
645 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
646 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
652 get_window_title_name (EmpathyChatWindow *self)
654 gchar *active_name, *ret;
656 guint current_unread_msgs;
658 nb_chats = g_list_length (self->priv->chats);
659 g_assert (nb_chats > 0);
661 active_name = empathy_chat_dup_name (self->priv->current_chat);
663 current_unread_msgs = empathy_chat_get_nb_unread_messages (
664 self->priv->current_chat);
669 if (current_unread_msgs == 0)
670 ret = g_strdup (active_name);
672 ret = g_strdup_printf (ngettext (
674 "%s (%d unread)", current_unread_msgs),
675 active_name, current_unread_msgs);
679 guint nb_others = nb_chats - 1;
680 guint all_unread_msgs;
682 all_unread_msgs = get_all_unread_messages (self);
684 if (all_unread_msgs == 0)
686 /* no unread message */
687 ret = g_strdup_printf (ngettext (
689 "%s (and %u others)", nb_others),
690 active_name, nb_others);
692 else if (all_unread_msgs == current_unread_msgs)
694 /* unread messages are in the current tab */
695 ret = g_strdup_printf (ngettext (
697 "%s (%d unread)", current_unread_msgs),
698 active_name, current_unread_msgs);
700 else if (current_unread_msgs == 0)
702 /* unread messages are in other tabs */
703 ret = g_strdup_printf (ngettext (
704 "%s (%d unread from others)",
705 "%s (%d unread from others)",
707 active_name, all_unread_msgs);
711 /* unread messages are in all the tabs */
712 ret = g_strdup_printf (ngettext (
713 "%s (%d unread from all)",
714 "%s (%d unread from all)",
716 active_name, all_unread_msgs);
720 g_free (active_name);
726 chat_window_title_update (EmpathyChatWindow *self)
730 name = get_window_title_name (self);
731 gtk_window_set_title (GTK_WINDOW (self->priv->dialog), name);
736 chat_window_icon_update (EmpathyChatWindow *self,
737 gboolean new_messages)
740 EmpathyContact *remote_contact;
741 gboolean avatar_in_icon;
744 n_chats = g_list_length (self->priv->chats);
746 /* Update window icon */
749 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog),
750 EMPATHY_IMAGE_MESSAGE);
754 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
755 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
757 if (n_chats == 1 && avatar_in_icon)
759 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
760 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
762 gtk_window_set_icon (GTK_WINDOW (self->priv->dialog), icon);
765 g_object_unref (icon);
769 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog), NULL);
775 chat_window_close_button_update (EmpathyChatWindow *self,
779 GtkWidget *chat_close_button;
784 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
785 chat_close_button = g_object_get_data (G_OBJECT (chat),
786 "chat-window-tab-close-button");
787 gtk_widget_hide (chat_close_button);
791 for (i=0; i<num_pages; i++)
793 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
794 chat_close_button = g_object_get_data (G_OBJECT (chat),
795 "chat-window-tab-close-button");
796 gtk_widget_show (chat_close_button);
802 chat_window_update (EmpathyChatWindow *self,
803 gboolean update_contact_menu)
807 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
809 /* Update Tab menu */
810 chat_window_menu_context_update (self, num_pages);
812 chat_window_conversation_menu_update (self);
814 /* If this update is due to a focus-in event, we know the menu will be
815 the same as when we last left it, so no work to do. Besides, if we
816 swap out the menu on a focus-in, we may confuse any external global
818 if (update_contact_menu)
820 chat_window_contact_menu_update (self);
823 chat_window_title_update (self);
825 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
827 chat_window_close_button_update (self, num_pages);
831 append_markup_printf (GString *string,
838 va_start (args, format);
840 tmp = g_markup_vprintf_escaped (format, args);
841 g_string_append (string, tmp);
848 chat_window_update_chat_tab_full (EmpathyChat *chat,
849 gboolean update_contact_menu)
851 EmpathyChatWindow *self;
852 EmpathyContact *remote_contact;
856 const gchar *subject;
857 const gchar *status = NULL;
861 const gchar *icon_name;
862 GtkWidget *tab_image;
863 GtkWidget *menu_image;
864 GtkWidget *sending_spinner;
867 self = chat_window_find_chat (chat);
871 /* Get information */
872 name = empathy_chat_dup_name (chat);
873 account = empathy_chat_get_account (chat);
874 subject = empathy_chat_get_subject (chat);
875 remote_contact = empathy_chat_get_remote_contact (chat);
877 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
879 name, tp_proxy_get_object_path (account), subject, remote_contact);
881 /* Update tab image */
882 if (empathy_chat_get_tp_chat (chat) == NULL)
884 /* No TpChat, we are disconnected */
887 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
889 icon_name = EMPATHY_IMAGE_MESSAGE;
891 else if (remote_contact && empathy_chat_is_composing (chat))
893 icon_name = EMPATHY_IMAGE_TYPING;
895 else if (empathy_chat_is_sms_channel (chat))
897 icon_name = EMPATHY_IMAGE_SMS;
899 else if (remote_contact)
901 icon_name = empathy_icon_name_for_contact (remote_contact);
905 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
908 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
909 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
911 if (icon_name != NULL)
913 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
915 gtk_widget_show (tab_image);
916 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
918 gtk_widget_show (menu_image);
922 gtk_widget_hide (tab_image);
923 gtk_widget_hide (menu_image);
926 /* Update the sending spinner */
927 nb_sending = empathy_chat_get_n_messages_sending (chat);
928 sending_spinner = g_object_get_data (G_OBJECT (chat),
929 "chat-window-tab-sending-spinner");
931 g_object_set (sending_spinner,
932 "active", nb_sending > 0,
933 "visible", nb_sending > 0,
936 /* Update tab tooltip */
937 tooltip = g_string_new (NULL);
941 id = empathy_contact_get_id (remote_contact);
942 status = empathy_contact_get_presence_message (remote_contact);
949 if (empathy_chat_is_sms_channel (chat))
950 append_markup_printf (tooltip, "%s ", _("SMS:"));
952 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
953 id, tp_account_get_display_name (account));
957 char *tmp = g_strdup_printf (
958 ngettext ("Sending %d message",
959 "Sending %d messages",
963 g_string_append (tooltip, "\n");
964 g_string_append (tooltip, tmp);
966 gtk_widget_set_tooltip_text (sending_spinner, tmp);
970 if (!EMP_STR_EMPTY (status))
971 append_markup_printf (tooltip, "\n<i>%s</i>", status);
973 if (!EMP_STR_EMPTY (subject))
974 append_markup_printf (tooltip, "\n<b>%s</b> %s",
975 _("Topic:"), subject);
977 if (remote_contact && empathy_chat_is_composing (chat))
978 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
980 if (remote_contact != NULL)
982 const gchar * const *types;
984 types = empathy_contact_get_client_types (remote_contact);
985 if (types != NULL && !tp_strdiff (types[0], "phone"))
987 /* I'm on a phone ! */
990 name = g_strdup_printf ("☎ %s", name);
995 markup = g_string_free (tooltip, FALSE);
996 widget = g_object_get_data (G_OBJECT (chat),
997 "chat-window-tab-tooltip-widget");
998 gtk_widget_set_tooltip_markup (widget, markup);
1000 widget = g_object_get_data (G_OBJECT (chat),
1001 "chat-window-menu-tooltip-widget");
1002 gtk_widget_set_tooltip_markup (widget, markup);
1005 /* Update tab and menu label */
1006 if (empathy_chat_is_highlighted (chat))
1008 markup = g_markup_printf_escaped (
1009 "<span color=\"red\" weight=\"bold\">%s</span>",
1014 markup = g_markup_escape_text (name, -1);
1017 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1018 gtk_label_set_markup (GTK_LABEL (widget), markup);
1019 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1020 gtk_label_set_markup (GTK_LABEL (widget), markup);
1023 /* Update the window if it's the current chat */
1024 if (self->priv->current_chat == chat)
1025 chat_window_update (self, update_contact_menu);
1031 chat_window_update_chat_tab (EmpathyChat *chat)
1033 chat_window_update_chat_tab_full (chat, TRUE);
1037 chat_window_chat_notify_cb (EmpathyChat *chat)
1039 EmpathyChatWindow *window;
1040 EmpathyContact *old_remote_contact;
1041 EmpathyContact *remote_contact = NULL;
1043 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1044 "chat-window-remote-contact");
1045 remote_contact = empathy_chat_get_remote_contact (chat);
1047 if (old_remote_contact != remote_contact)
1049 /* The remote-contact associated with the chat changed, we need
1050 * to keep track of any change of that contact and update the
1051 * window each time. */
1053 g_signal_connect_swapped (remote_contact, "notify",
1054 G_CALLBACK (chat_window_update_chat_tab), chat);
1056 if (old_remote_contact)
1057 g_signal_handlers_disconnect_by_func (old_remote_contact,
1058 chat_window_update_chat_tab, chat);
1060 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1061 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1064 chat_window_update_chat_tab (chat);
1066 window = chat_window_find_chat (chat);
1068 chat_window_update (window, FALSE);
1072 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1073 EmpathySmiley *smiley,
1076 EmpathyChatWindow *self = user_data;
1078 GtkTextBuffer *buffer;
1081 chat = self->priv->current_chat;
1083 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1084 gtk_text_buffer_get_end_iter (buffer, &iter);
1085 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1089 chat_window_conv_activate_cb (GtkAction *action,
1090 EmpathyChatWindow *self)
1094 EmpathyContact *remote_contact = NULL;
1096 /* Favorite room menu */
1097 is_room = empathy_chat_is_room (self->priv->current_chat);
1102 gboolean found = FALSE;
1103 EmpathyChatroom *chatroom;
1105 room = empathy_chat_get_id (self->priv->current_chat);
1106 account = empathy_chat_get_account (self->priv->current_chat);
1107 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1110 if (chatroom != NULL)
1111 found = empathy_chatroom_is_favorite (chatroom);
1113 DEBUG ("This room %s favorite", found ? "is" : "is not");
1114 gtk_toggle_action_set_active (
1115 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1117 if (chatroom != NULL)
1118 found = empathy_chatroom_is_always_urgent (chatroom);
1120 gtk_toggle_action_set_active (
1121 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1124 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1125 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1127 /* Show contacts menu */
1128 g_object_get (self->priv->current_chat,
1129 "remote-contact", &remote_contact,
1130 "show-contacts", &active,
1133 if (remote_contact == NULL)
1135 gtk_toggle_action_set_active (
1136 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1139 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1140 (remote_contact == NULL));
1142 if (remote_contact != NULL)
1143 g_object_unref (remote_contact);
1147 chat_window_clear_activate_cb (GtkAction *action,
1148 EmpathyChatWindow *self)
1150 empathy_chat_clear (self->priv->current_chat);
1154 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1155 EmpathyChatWindow *self)
1161 EmpathyChatroom *chatroom;
1163 active = gtk_toggle_action_get_active (toggle_action);
1164 account = empathy_chat_get_account (self->priv->current_chat);
1165 room = empathy_chat_get_id (self->priv->current_chat);
1166 name = empathy_chat_dup_name (self->priv->current_chat);
1168 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1169 account, room, name);
1171 empathy_chatroom_set_favorite (chatroom, active);
1172 g_object_unref (chatroom);
1177 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1178 EmpathyChatWindow *self)
1184 EmpathyChatroom *chatroom;
1186 active = gtk_toggle_action_get_active (toggle_action);
1187 account = empathy_chat_get_account (self->priv->current_chat);
1188 room = empathy_chat_get_id (self->priv->current_chat);
1189 name = empathy_chat_dup_name (self->priv->current_chat);
1191 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1192 account, room, name);
1194 empathy_chatroom_set_always_urgent (chatroom, active);
1195 g_object_unref (chatroom);
1200 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1201 EmpathyChatWindow *self)
1205 active = gtk_toggle_action_get_active (toggle_action);
1207 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1211 chat_window_invite_participant_activate_cb (GtkAction *action,
1212 EmpathyChatWindow *self)
1215 EmpathyTpChat *tp_chat;
1218 g_return_if_fail (self->priv->current_chat != NULL);
1220 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1222 dialog = empathy_invite_participant_dialog_new (
1223 GTK_WINDOW (self->priv->dialog), tp_chat);
1225 gtk_widget_show (dialog);
1227 response = gtk_dialog_run (GTK_DIALOG (dialog));
1229 if (response == GTK_RESPONSE_ACCEPT)
1231 TpContact *tp_contact;
1232 EmpathyContact *contact;
1234 tp_contact = empathy_invite_participant_dialog_get_selected (
1235 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1236 if (tp_contact == NULL)
1239 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1241 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1243 g_object_unref (contact);
1247 gtk_widget_destroy (dialog);
1251 chat_window_close_activate_cb (GtkAction *action,
1252 EmpathyChatWindow *self)
1254 g_return_if_fail (self->priv->current_chat != NULL);
1256 maybe_close_chat (self, self->priv->current_chat);
1260 chat_window_edit_activate_cb (GtkAction *action,
1261 EmpathyChatWindow *self)
1263 GtkClipboard *clipboard;
1264 GtkTextBuffer *buffer;
1265 gboolean text_available;
1267 g_return_if_fail (self->priv->current_chat != NULL);
1269 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1271 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1272 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1273 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1277 buffer = gtk_text_view_get_buffer (
1278 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1280 if (gtk_text_buffer_get_has_selection (buffer))
1282 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1283 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1289 selection = empathy_theme_adium_get_has_selection (
1290 self->priv->current_chat->view);
1292 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1293 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1296 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1297 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1298 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1302 chat_window_cut_activate_cb (GtkAction *action,
1303 EmpathyChatWindow *self)
1305 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1307 empathy_chat_cut (self->priv->current_chat);
1311 chat_window_copy_activate_cb (GtkAction *action,
1312 EmpathyChatWindow *self)
1314 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1316 empathy_chat_copy (self->priv->current_chat);
1320 chat_window_paste_activate_cb (GtkAction *action,
1321 EmpathyChatWindow *self)
1323 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1325 empathy_chat_paste (self->priv->current_chat);
1329 chat_window_find_activate_cb (GtkAction *action,
1330 EmpathyChatWindow *self)
1332 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1334 empathy_chat_find (self->priv->current_chat);
1338 chat_window_tabs_next_activate_cb (GtkAction *action,
1339 EmpathyChatWindow *self)
1341 gint index_, numPages;
1342 gboolean wrap_around;
1344 g_object_get (gtk_settings_get_default (),
1345 "gtk-keynav-wrap-around", &wrap_around,
1348 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1349 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1351 if (index_ == (numPages - 1) && wrap_around)
1353 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1357 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1361 chat_window_tabs_previous_activate_cb (GtkAction *action,
1362 EmpathyChatWindow *self)
1364 gint index_, numPages;
1365 gboolean wrap_around;
1367 g_object_get (gtk_settings_get_default (),
1368 "gtk-keynav-wrap-around", &wrap_around,
1371 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1372 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1374 if (index_ <= 0 && wrap_around)
1376 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1381 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1385 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1386 EmpathyChatWindow *self)
1388 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1389 empathy_get_current_action_time ());
1393 chat_window_tabs_left_activate_cb (GtkAction *action,
1394 EmpathyChatWindow *self)
1397 gint index_, num_pages;
1399 chat = self->priv->current_chat;
1400 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1404 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1407 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1408 chat_window_menu_context_update (self, num_pages);
1412 chat_window_tabs_right_activate_cb (GtkAction *action,
1413 EmpathyChatWindow *self)
1416 gint index_, num_pages;
1418 chat = self->priv->current_chat;
1419 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1421 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1424 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1425 chat_window_menu_context_update (self, num_pages);
1428 static EmpathyChatWindow *
1429 empathy_chat_window_new (void)
1431 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1435 chat_window_detach_activate_cb (GtkAction *action,
1436 EmpathyChatWindow *self)
1438 EmpathyChatWindow *new_window;
1441 chat = self->priv->current_chat;
1442 new_window = empathy_chat_window_new ();
1444 empathy_chat_window_move_chat (self, new_window, chat);
1446 gtk_widget_show (new_window->priv->dialog);
1450 chat_window_help_contents_activate_cb (GtkAction *action,
1451 EmpathyChatWindow *self)
1453 empathy_url_show (self->priv->dialog, "help:empathy");
1457 chat_window_help_about_activate_cb (GtkAction *action,
1458 EmpathyChatWindow *self)
1460 empathy_about_dialog_new (GTK_WINDOW (self->priv->dialog));
1464 chat_window_delete_event_cb (GtkWidget *dialog,
1466 EmpathyChatWindow *self)
1468 EmpathyChat *chat = NULL;
1472 DEBUG ("Delete event received");
1474 for (l = self->priv->chats; l != NULL; l = l->next)
1476 if (chat_needs_close_confirmation (l->data))
1485 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1489 remove_all_chats (self);
1496 chat_window_composing_cb (EmpathyChat *chat,
1497 gboolean is_composing,
1498 EmpathyChatWindow *self)
1500 chat_window_update_chat_tab (chat);
1504 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1507 gtk_window_set_urgency_hint (GTK_WINDOW (self->priv->dialog), urgent);
1511 chat_window_notification_closed_cb (NotifyNotification *notify,
1512 EmpathyChatWindow *self)
1514 g_object_unref (notify);
1515 if (self->priv->notification == notify)
1516 self->priv->notification = NULL;
1520 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1521 EmpathyMessage *message,
1524 EmpathyContact *sender;
1525 const gchar *header;
1529 gboolean res, has_x_canonical_append;
1530 NotifyNotification *notification = self->priv->notification;
1532 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1535 res = g_settings_get_boolean (self->priv->gsettings_notif,
1536 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1541 sender = empathy_message_get_sender (message);
1542 header = empathy_contact_get_alias (sender);
1543 body = empathy_message_get_body (message);
1544 escaped = g_markup_escape_text (body, -1);
1546 has_x_canonical_append = empathy_notify_manager_has_capability (
1547 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1549 if (notification != NULL && !has_x_canonical_append)
1551 /* if the notification server supports x-canonical-append, it is
1552 better to not use notify_notification_update to avoid
1553 overwriting the current notification message */
1554 notify_notification_update (notification,
1555 header, escaped, NULL);
1559 /* if the notification server supports x-canonical-append,
1560 the hint will be added, so that the message from the
1561 just created notification will be automatically appended
1562 to an existing notification with the same title.
1563 In this way the previous message will not be lost: the new
1564 message will appear below it, in the same notification */
1565 const gchar *category = empathy_chat_is_room (chat)
1566 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1567 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1568 notification = notify_notification_new (header, escaped, NULL);
1570 if (self->priv->notification == NULL)
1571 self->priv->notification = notification;
1573 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1575 tp_g_signal_connect_object (notification, "closed",
1576 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1578 if (has_x_canonical_append)
1580 /* We have to set a not empty string to keep libnotify happy */
1581 notify_notification_set_hint_string (notification,
1582 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1585 notify_notification_set_hint (notification,
1586 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1589 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1590 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1594 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1595 g_object_unref (pixbuf);
1598 notify_notification_show (notification, NULL);
1604 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1608 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1610 g_object_get (self->priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1616 chat_window_new_message_cb (EmpathyChat *chat,
1617 EmpathyMessage *message,
1619 gboolean should_highlight,
1620 EmpathyChatWindow *self)
1623 gboolean needs_urgency;
1624 EmpathyContact *sender;
1626 has_focus = empathy_chat_window_has_focus (self);
1628 /* - if we're the sender, we play the sound if it's specified in the
1629 * preferences and we're not away.
1630 * - if we receive a message, we play the sound if it's specified in the
1631 * preferences and the window does not have focus on the chat receiving
1635 sender = empathy_message_get_sender (message);
1637 if (empathy_contact_is_user (sender))
1639 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self->priv->dialog),
1640 EMPATHY_SOUND_MESSAGE_OUTGOING);
1644 if (has_focus && self->priv->current_chat == chat)
1646 /* window and tab are focused so consider the message to be read */
1648 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1649 empathy_chat_messages_read (chat);
1653 /* Update the chat tab if this is the first unread message */
1654 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1656 chat_window_update_chat_tab (chat);
1659 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1660 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1661 * an unamed MUC (msn-like).
1662 * In case of a MUC, we set urgency if either:
1663 * a) the chatroom's always_urgent property is TRUE
1664 * b) the message contains our alias
1666 if (empathy_chat_is_room (chat))
1670 EmpathyChatroom *chatroom;
1672 account = empathy_chat_get_account (chat);
1673 room = empathy_chat_get_id (chat);
1675 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1678 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1679 needs_urgency = TRUE;
1681 needs_urgency = should_highlight;
1685 needs_urgency = TRUE;
1691 chat_window_set_urgency_hint (self, TRUE);
1693 /* Pending messages have already been displayed and notified in the
1694 * approver, so we don't display a notification and play a sound
1698 empathy_sound_manager_play (self->priv->sound_mgr,
1699 GTK_WIDGET (self->priv->dialog), EMPATHY_SOUND_MESSAGE_INCOMING);
1701 chat_window_show_or_update_notification (self, message, chat);
1705 /* update the number of unread messages and the window icon */
1706 chat_window_title_update (self);
1707 chat_window_icon_update (self, TRUE);
1711 chat_window_command_part (EmpathyChat *chat,
1714 EmpathyChat *chat_to_be_parted;
1715 EmpathyTpChat *tp_chat = NULL;
1717 if (strv[1] == NULL)
1719 /* No chatroom ID specified */
1720 tp_chat = empathy_chat_get_tp_chat (chat);
1723 empathy_tp_chat_leave (tp_chat, "");
1728 chat_to_be_parted = empathy_chat_window_find_chat (
1729 empathy_chat_get_account (chat), strv[1], FALSE);
1731 if (chat_to_be_parted != NULL)
1733 /* Found a chatroom matching the specified ID */
1734 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1737 empathy_tp_chat_leave (tp_chat, strv[2]);
1743 /* Going by the syntax of PART command:
1745 * /PART [<chatroom-ID>] [<reason>]
1747 * Chatroom-ID is not a must to specify a reason.
1748 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1749 * MUC then the current chatroom should be parted and srtv[1] should
1750 * be treated as part of the optional part-message. */
1751 message = g_strconcat (strv[1], " ", strv[2], NULL);
1752 tp_chat = empathy_chat_get_tp_chat (chat);
1755 empathy_tp_chat_leave (tp_chat, message);
1761 static GtkNotebook *
1762 notebook_create_window_cb (GtkNotebook *source,
1768 EmpathyChatWindow *window, *new_window;
1771 chat = EMPATHY_CHAT (page);
1772 window = chat_window_find_chat (chat);
1774 new_window = empathy_chat_window_new ();
1776 DEBUG ("Detach hook called");
1778 empathy_chat_window_move_chat (window, new_window, chat);
1780 gtk_widget_show (new_window->priv->dialog);
1781 gtk_window_move (GTK_WINDOW (new_window->priv->dialog), x, y);
1787 chat_window_page_switched_cb (GtkNotebook *notebook,
1790 EmpathyChatWindow *self)
1792 EmpathyChat *chat = EMPATHY_CHAT (child);
1794 DEBUG ("Page switched");
1796 if (self->priv->page_added)
1798 self->priv->page_added = FALSE;
1799 empathy_chat_scroll_down (chat);
1801 else if (self->priv->current_chat == chat)
1806 self->priv->current_chat = chat;
1807 empathy_chat_messages_read (chat);
1809 chat_window_update_chat_tab (chat);
1813 chat_window_page_added_cb (GtkNotebook *notebook,
1816 EmpathyChatWindow *self)
1820 /* If we just received DND to the same window, we don't want
1821 * to do anything here like removing the tab and then readding
1822 * it, so we return here and in "page-added".
1824 if (self->priv->dnd_same_window)
1826 DEBUG ("Page added (back to the same window)");
1827 self->priv->dnd_same_window = FALSE;
1831 DEBUG ("Page added");
1833 /* Get chat object */
1834 chat = EMPATHY_CHAT (child);
1836 /* Connect chat signals for this window */
1837 g_signal_connect (chat, "composing",
1838 G_CALLBACK (chat_window_composing_cb), self);
1839 g_signal_connect (chat, "new-message",
1840 G_CALLBACK (chat_window_new_message_cb), self);
1841 g_signal_connect (chat, "part-command-entered",
1842 G_CALLBACK (chat_window_command_part), NULL);
1843 g_signal_connect (chat, "notify::tp-chat",
1844 G_CALLBACK (chat_window_update_chat_tab), self);
1846 /* Set flag so we know to perform some special operations on
1847 * switch page due to the new page being added.
1849 self->priv->page_added = TRUE;
1851 /* Get list of chats up to date */
1852 self->priv->chats = g_list_append (self->priv->chats, chat);
1854 chat_window_update_chat_tab (chat);
1858 chat_window_page_removed_cb (GtkNotebook *notebook,
1861 EmpathyChatWindow *self)
1865 /* If we just received DND to the same window, we don't want
1866 * to do anything here like removing the tab and then readding
1867 * it, so we return here and in "page-added".
1869 if (self->priv->dnd_same_window)
1871 DEBUG ("Page removed (and will be readded to same window)");
1875 DEBUG ("Page removed");
1877 /* Get chat object */
1878 chat = EMPATHY_CHAT (child);
1880 /* Disconnect all signal handlers for this chat and this window */
1881 g_signal_handlers_disconnect_by_func (chat,
1882 G_CALLBACK (chat_window_composing_cb), self);
1883 g_signal_handlers_disconnect_by_func (chat,
1884 G_CALLBACK (chat_window_new_message_cb), self);
1885 g_signal_handlers_disconnect_by_func (chat,
1886 G_CALLBACK (chat_window_update_chat_tab), self);
1888 /* Keep list of chats up to date */
1889 self->priv->chats = g_list_remove (self->priv->chats, chat);
1890 empathy_chat_messages_read (chat);
1892 if (self->priv->chats == NULL)
1894 g_object_unref (self);
1898 chat_window_update (self, TRUE);
1903 chat_window_focus_in_event_cb (GtkWidget *widget,
1905 EmpathyChatWindow *self)
1907 empathy_chat_messages_read (self->priv->current_chat);
1909 chat_window_set_urgency_hint (self, FALSE);
1911 /* Update the title, since we now mark all unread messages as read. */
1912 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1918 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1919 EmpathyChatWindow *self)
1921 chat_window_contact_menu_update (self);
1925 chat_window_focus_out_event_cb (GtkWidget *widget,
1927 EmpathyChatWindow *self)
1929 if (self->priv->individual_mgr != NULL)
1932 /* Keep the individual manager alive so we won't fetch everything from Folks
1933 * each time we need to use it. Loading FolksAggregator can takes quite a
1934 * while (if user has a huge LDAP abook for example) and it blocks
1935 * the mainloop during most of this loading. We workaround this by loading
1936 * it when the chat window has been unfocused and so, hopefully, not impact
1937 * the reactivity of the chat window too much.
1939 * The individual manager (and so Folks) is needed to know to which
1940 * FolksIndividual a TpContact belongs, including:
1941 * - empathy_chat_get_contact_menu: to list all the personas of the contact
1942 * - empathy_display_individual_info: to invoke gnome-contacts with the
1943 * FolksIndividual.id of the contact
1944 * - drag_data_received_individual_id: to find the individual associated
1945 * with the ID we received from the DnD in order to invite him.
1947 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
1949 if (!empathy_individual_manager_get_contacts_loaded (
1950 self->priv->individual_mgr))
1952 /* We want to update the contact menu when Folks is loaded so we can
1953 * list all the personas of the contact. */
1954 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
1955 G_CALLBACK (contacts_loaded_cb), self, 0);
1962 chat_window_drag_drop (GtkWidget *widget,
1963 GdkDragContext *context,
1967 EmpathyChatWindow *self)
1971 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1972 if (target == GDK_NONE)
1973 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1975 if (target != GDK_NONE)
1977 gtk_drag_get_data (widget, context, target, time_);
1985 chat_window_drag_motion (GtkWidget *widget,
1986 GdkDragContext *context,
1990 EmpathyChatWindow *self)
1994 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1996 if (target != GDK_NONE)
1998 /* This is a file drag. Ensure the contact is online and set the
1999 drag type to COPY. Note that it's possible that the tab will
2000 be switched by GTK+ after a timeout from drag_motion without
2001 getting another drag_motion to disable the drop. You have
2002 to hold your mouse really still.
2004 EmpathyContact *contact;
2006 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2008 /* contact is NULL for multi-user chats. We don't do
2009 * file transfers to MUCs. We also don't send files
2010 * to offline contacts or contacts that don't support
2013 if ((contact == NULL) || !empathy_contact_is_online (contact))
2015 gdk_drag_status (context, 0, time_);
2019 if (!(empathy_contact_get_capabilities (contact)
2020 & EMPATHY_CAPABILITIES_FT))
2022 gdk_drag_status (context, 0, time_);
2026 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2030 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2031 if (target != GDK_NONE)
2033 /* This is a drag of a contact from a contact list. Set to COPY.
2034 FIXME: If this drag is to a MUC window, it invites the user.
2035 Otherwise, it opens a chat. Should we use a different drag
2036 type for invites? Should we allow ASK?
2038 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2046 drag_data_received_individual_id (EmpathyChatWindow *self,
2048 GdkDragContext *context,
2051 GtkSelectionData *selection,
2056 FolksIndividual *individual;
2057 EmpathyTpChat *chat;
2058 TpContact *tp_contact;
2060 EmpathyContact *contact;
2062 id = (const gchar *) gtk_selection_data_get_data (selection);
2064 DEBUG ("DND invididual %s", id);
2066 if (self->priv->current_chat == NULL)
2069 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2073 if (!empathy_tp_chat_can_add_contact (chat))
2075 DEBUG ("Can't invite contact to %s",
2076 tp_proxy_get_object_path (chat));
2080 if (self->priv->individual_mgr == NULL)
2081 /* Not likely as we have to focus out the chat window in order to start
2082 * the DnD but best to be safe. */
2085 individual = empathy_individual_manager_lookup_member (
2086 self->priv->individual_mgr, id);
2087 if (individual == NULL)
2089 DEBUG ("Failed to find individual %s", id);
2093 conn = tp_channel_get_connection ((TpChannel *) chat);
2094 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2095 if (tp_contact == NULL)
2097 DEBUG ("Can't find a TpContact on connection %s for %s",
2098 tp_proxy_get_object_path (conn), id);
2102 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2103 tp_channel_get_identifier ((TpChannel *) chat));
2105 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2106 empathy_tp_chat_add (chat, contact, NULL);
2107 g_object_unref (contact);
2110 gtk_drag_finish (context, TRUE, FALSE, time_);
2114 chat_window_drag_data_received (GtkWidget *widget,
2115 GdkDragContext *context,
2118 GtkSelectionData *selection,
2121 EmpathyChatWindow *self)
2123 if (info == DND_DRAG_TYPE_CONTACT_ID)
2125 EmpathyChat *chat = NULL;
2126 EmpathyChatWindow *old_window;
2127 TpAccount *account = NULL;
2128 EmpathyClientFactory *factory;
2131 const gchar *account_id;
2132 const gchar *contact_id;
2134 id = (const gchar*) gtk_selection_data_get_data (selection);
2136 factory = empathy_client_factory_dup ();
2138 DEBUG ("DND contact from roster with id:'%s'", id);
2140 strv = g_strsplit (id, ":", 2);
2141 if (g_strv_length (strv) == 2)
2143 account_id = strv[0];
2144 contact_id = strv[1];
2146 account = tp_simple_client_factory_ensure_account (
2147 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2149 g_object_unref (factory);
2150 if (account != NULL)
2151 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2154 if (account == NULL)
2157 gtk_drag_finish (context, FALSE, FALSE, time_);
2163 empathy_chat_with_contact_id (account, contact_id,
2164 empathy_get_current_action_time (), NULL, NULL);
2172 old_window = chat_window_find_chat (chat);
2175 if (old_window == self)
2177 gtk_drag_finish (context, TRUE, FALSE, time_);
2181 empathy_chat_window_move_chat (old_window, self, chat);
2185 empathy_chat_window_add_chat (self, chat);
2188 /* Added to take care of any outstanding chat events */
2189 empathy_chat_window_present_chat (chat,
2190 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2192 /* We should return TRUE to remove the data when doing
2193 * GDK_ACTION_MOVE, but we don't here otherwise it has
2194 * weird consequences, and we handle that internally
2195 * anyway with add_chat () and remove_chat ().
2197 gtk_drag_finish (context, TRUE, FALSE, time_);
2199 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2201 drag_data_received_individual_id (self, widget, context, x, y,
2202 selection, info, time_);
2204 else if (info == DND_DRAG_TYPE_URI_LIST)
2206 EmpathyContact *contact;
2209 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2211 /* contact is NULL when current_chat is a multi-user chat.
2212 * We don't do file transfers to MUCs, so just cancel the drag.
2214 if (contact == NULL)
2216 gtk_drag_finish (context, TRUE, FALSE, time_);
2220 data = (const gchar *) gtk_selection_data_get_data (selection);
2221 empathy_send_file_from_uri_list (contact, data);
2223 gtk_drag_finish (context, TRUE, FALSE, time_);
2225 else if (info == DND_DRAG_TYPE_TAB)
2228 EmpathyChatWindow *old_window = NULL;
2232 chat = (void *) gtk_selection_data_get_data (selection);
2233 old_window = chat_window_find_chat (*chat);
2237 self->priv->dnd_same_window = (old_window == self);
2239 DEBUG ("DND tab (within same window: %s)",
2240 self->priv->dnd_same_window ? "Yes" : "No");
2245 DEBUG ("DND from unknown source");
2246 gtk_drag_finish (context, FALSE, FALSE, time_);
2251 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2252 guint num_chats_in_manager,
2253 EmpathyChatWindow *self)
2255 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2256 num_chats_in_manager > 0);
2260 chat_window_finalize (GObject *object)
2262 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2264 DEBUG ("Finalized: %p", object);
2266 g_object_unref (self->priv->ui_manager);
2267 g_object_unref (self->priv->chatroom_manager);
2268 g_object_unref (self->priv->notify_mgr);
2269 g_object_unref (self->priv->gsettings_chat);
2270 g_object_unref (self->priv->gsettings_notif);
2271 g_object_unref (self->priv->gsettings_ui);
2272 g_object_unref (self->priv->sound_mgr);
2273 g_clear_object (&self->priv->individual_mgr);
2275 if (self->priv->notification != NULL)
2277 notify_notification_close (self->priv->notification, NULL);
2278 self->priv->notification = NULL;
2281 if (self->priv->contact_targets)
2282 gtk_target_list_unref (self->priv->contact_targets);
2284 if (self->priv->file_targets)
2285 gtk_target_list_unref (self->priv->file_targets);
2287 if (self->priv->chat_manager)
2289 g_signal_handler_disconnect (self->priv->chat_manager,
2290 self->priv->chat_manager_chats_changed_id);
2291 g_object_unref (self->priv->chat_manager);
2292 self->priv->chat_manager = NULL;
2295 chat_windows = g_list_remove (chat_windows, self);
2296 gtk_widget_destroy (self->priv->dialog);
2298 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2302 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2304 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2306 object_class->finalize = chat_window_finalize;
2308 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2312 empathy_chat_window_init (EmpathyChatWindow *self)
2315 GtkAccelGroup *accel_group;
2320 GtkWidget *chat_vbox;
2322 EmpathySmileyManager *smiley_manager;
2324 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2325 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2327 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2328 gui = empathy_builder_get_file (filename,
2329 "chat_window", &self->priv->dialog,
2330 "chat_vbox", &chat_vbox,
2331 "ui_manager", &self->priv->ui_manager,
2332 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2333 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2334 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2335 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2336 "menu_edit_cut", &self->priv->menu_edit_cut,
2337 "menu_edit_copy", &self->priv->menu_edit_copy,
2338 "menu_edit_paste", &self->priv->menu_edit_paste,
2339 "menu_edit_find", &self->priv->menu_edit_find,
2340 "menu_tabs_next", &self->priv->menu_tabs_next,
2341 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2342 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2343 "menu_tabs_left", &self->priv->menu_tabs_left,
2344 "menu_tabs_right", &self->priv->menu_tabs_right,
2345 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2349 empathy_builder_connect (gui, self,
2350 "menu_conv", "activate", chat_window_conv_activate_cb,
2351 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2352 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2353 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2354 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2355 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2356 "menu_conv_close", "activate", chat_window_close_activate_cb,
2357 "menu_edit", "activate", chat_window_edit_activate_cb,
2358 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2359 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2360 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2361 "menu_edit_find", "activate", chat_window_find_activate_cb,
2362 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2363 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2364 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2365 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2366 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2367 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2368 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2369 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2372 g_object_ref (self->priv->ui_manager);
2373 g_object_unref (gui);
2375 empathy_set_css_provider (GTK_WIDGET (self->priv->dialog));
2377 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2378 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2379 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2380 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2382 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2384 self->priv->notebook = gtk_notebook_new ();
2386 g_signal_connect (self->priv->notebook, "create-window",
2387 G_CALLBACK (notebook_create_window_cb), self);
2389 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2390 "EmpathyChatWindow");
2391 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2392 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2393 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2394 gtk_widget_show (self->priv->notebook);
2397 accel_group = gtk_accel_group_new ();
2398 gtk_window_add_accel_group (GTK_WINDOW (self->priv->dialog), accel_group);
2400 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2402 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2405 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2409 g_object_unref (accel_group);
2411 /* Set up drag target lists */
2412 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2413 G_N_ELEMENTS (drag_types_dest_contact));
2415 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2416 G_N_ELEMENTS (drag_types_dest_file));
2418 /* Set up smiley menu */
2419 smiley_manager = empathy_smiley_manager_dup_singleton ();
2420 submenu = empathy_smiley_menu_new (smiley_manager,
2421 chat_window_insert_smiley_activate_cb, self);
2423 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2424 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2425 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2426 g_object_unref (smiley_manager);
2428 /* Set up signals we can't do with ui file since we may need to
2429 * block/unblock them at some later stage.
2432 g_signal_connect (self->priv->dialog, "delete_event",
2433 G_CALLBACK (chat_window_delete_event_cb), self);
2434 g_signal_connect (self->priv->dialog, "focus_in_event",
2435 G_CALLBACK (chat_window_focus_in_event_cb), self);
2436 g_signal_connect (self->priv->dialog, "focus_out_event",
2437 G_CALLBACK (chat_window_focus_out_event_cb), self);
2438 g_signal_connect_after (self->priv->notebook, "switch_page",
2439 G_CALLBACK (chat_window_page_switched_cb), self);
2440 g_signal_connect (self->priv->notebook, "page_added",
2441 G_CALLBACK (chat_window_page_added_cb), self);
2442 g_signal_connect (self->priv->notebook, "page_removed",
2443 G_CALLBACK (chat_window_page_removed_cb), self);
2445 /* Set up drag and drop */
2446 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2447 GTK_DEST_DEFAULT_HIGHLIGHT,
2449 G_N_ELEMENTS (drag_types_dest),
2450 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2452 /* connect_after to allow GtkNotebook's built-in tab switching */
2453 g_signal_connect_after (self->priv->notebook, "drag-motion",
2454 G_CALLBACK (chat_window_drag_motion), self);
2455 g_signal_connect (self->priv->notebook, "drag-data-received",
2456 G_CALLBACK (chat_window_drag_data_received), self);
2457 g_signal_connect (self->priv->notebook, "drag-drop",
2458 G_CALLBACK (chat_window_drag_drop), self);
2460 chat_windows = g_list_prepend (chat_windows, self);
2462 /* Set up private details */
2463 self->priv->chats = NULL;
2464 self->priv->current_chat = NULL;
2465 self->priv->notification = NULL;
2467 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2469 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2470 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2471 self->priv->chat_manager, "closed-chats-changed",
2472 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2474 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2475 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2478 /* Returns the window to open a new tab in if there is a suitable window,
2479 * otherwise, returns NULL indicating that a new window should be added.
2481 static EmpathyChatWindow *
2482 empathy_chat_window_get_default (gboolean room)
2484 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2486 gboolean separate_windows = TRUE;
2488 separate_windows = g_settings_get_boolean (gsettings,
2489 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2491 g_object_unref (gsettings);
2493 if (separate_windows)
2494 /* Always create a new window */
2497 for (l = chat_windows; l; l = l->next)
2499 EmpathyChatWindow *chat_window;
2500 guint nb_rooms, nb_private;
2502 chat_window = l->data;
2504 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2506 /* Skip the window if there aren't any rooms in it */
2507 if (room && nb_rooms == 0)
2510 /* Skip the window if there aren't any 1-1 chats in it */
2511 if (!room && nb_private == 0)
2521 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2525 GtkWidget *popup_label;
2527 GValue value = { 0, };
2529 g_return_if_fail (self != NULL);
2530 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2532 /* Reference the chat object */
2533 g_object_ref (chat);
2535 /* If this window has just been created, position it */
2536 if (self->priv->chats == NULL)
2538 const gchar *name = "chat-window";
2539 gboolean separate_windows;
2541 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2542 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2544 if (empathy_chat_is_room (chat))
2545 name = "room-window";
2547 if (separate_windows)
2551 /* Save current position of the window */
2552 gtk_window_get_position (GTK_WINDOW (self->priv->dialog), &x, &y);
2554 /* First bind to the 'generic' name. So new window for which we didn't
2555 * save a geometry yet will have the geometry of the last saved
2556 * window (bgo #601191). */
2557 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2559 /* Restore previous position of the window so the newly created window
2560 * won't be in the same position as the latest saved window and so
2561 * completely hide it. */
2562 gtk_window_move (GTK_WINDOW (self->priv->dialog), x, y);
2564 /* Then bind it to the name of the contact/room so we'll save the
2565 * geometry specific to this window */
2566 name = empathy_chat_get_id (chat);
2569 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2572 child = GTK_WIDGET (chat);
2573 label = chat_window_create_label (self, chat, TRUE);
2574 popup_label = chat_window_create_label (self, chat, FALSE);
2575 gtk_widget_show (child);
2577 g_signal_connect (chat, "notify::name",
2578 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2579 g_signal_connect (chat, "notify::subject",
2580 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2581 g_signal_connect (chat, "notify::remote-contact",
2582 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2583 g_signal_connect (chat, "notify::sms-channel",
2584 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2585 g_signal_connect (chat, "notify::n-messages-sending",
2586 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2587 g_signal_connect (chat, "notify::nb-unread-messages",
2588 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2589 chat_window_chat_notify_cb (chat);
2591 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2593 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2594 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2595 g_value_init (&value, G_TYPE_BOOLEAN);
2596 g_value_set_boolean (&value, TRUE);
2597 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2598 child, "tab-expand" , &value);
2599 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2600 child, "tab-fill" , &value);
2601 g_value_unset (&value);
2603 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2607 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2611 EmpathyContact *remote_contact;
2612 EmpathyChatManager *chat_manager;
2614 g_return_if_fail (self != NULL);
2615 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2617 g_signal_handlers_disconnect_by_func (chat,
2618 chat_window_chat_notify_cb, NULL);
2620 remote_contact = g_object_get_data (G_OBJECT (chat),
2621 "chat-window-remote-contact");
2625 g_signal_handlers_disconnect_by_func (remote_contact,
2626 chat_window_update_chat_tab, chat);
2629 chat_manager = empathy_chat_manager_dup_singleton ();
2630 empathy_chat_manager_closed_chat (chat_manager, chat);
2631 g_object_unref (chat_manager);
2633 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2635 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2637 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2639 g_object_unref (chat);
2643 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2644 EmpathyChatWindow *new_window,
2649 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2650 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2651 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2653 widget = GTK_WIDGET (chat);
2655 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2656 G_OBJECT (widget)->ref_count);
2658 /* We reference here to make sure we don't loose the widget
2659 * and the EmpathyChat object during the move.
2661 g_object_ref (chat);
2662 g_object_ref (widget);
2664 empathy_chat_window_remove_chat (old_window, chat);
2665 empathy_chat_window_add_chat (new_window, chat);
2667 g_object_unref (widget);
2668 g_object_unref (chat);
2672 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2677 g_return_if_fail (self != NULL);
2678 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2680 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2683 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2688 empathy_chat_window_find_chat (TpAccount *account,
2690 gboolean sms_channel)
2694 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2696 for (l = chat_windows; l; l = l->next)
2698 EmpathyChatWindow *window = l->data;
2701 for (ll = window->priv->chats; ll; ll = ll->next)
2707 if (account == empathy_chat_get_account (chat) &&
2708 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2709 sms_channel == empathy_chat_is_sms_channel (chat))
2718 empathy_chat_window_present_chat (EmpathyChat *chat,
2721 EmpathyChatWindow *self;
2722 guint32 x_timestamp;
2724 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2726 self = chat_window_find_chat (chat);
2728 /* If the chat has no window, create one */
2731 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2734 self = empathy_chat_window_new ();
2736 /* we want to display the newly created window even if we
2737 * don't present it */
2738 gtk_widget_show (self->priv->dialog);
2741 empathy_chat_window_add_chat (self, chat);
2744 /* Don't force the window to show itself when it wasn't
2745 * an action by the user
2747 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2750 if (x_timestamp != GDK_CURRENT_TIME)
2752 /* Don't present or switch tab if the action was earlier than the
2753 * last actions X time, accounting for overflow and the first ever
2756 if (self->priv->x_user_action_time != 0
2757 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2760 self->priv->x_user_action_time = x_timestamp;
2763 empathy_chat_window_switch_to_chat (self, chat);
2765 /* Don't use empathy_window_present_with_time () which would move the window
2766 * to our current desktop but move to the window's desktop instead. This is
2767 * more coherent with Shell's 'app is ready' notication which moves the view
2768 * to the app desktop rather than moving the app itself. */
2769 empathy_move_to_window_desktop (GTK_WINDOW (self->priv->dialog), x_timestamp);
2771 gtk_widget_grab_focus (chat->input_text_view);
2776 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2781 guint _nb_rooms = 0, _nb_private = 0;
2783 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2785 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2791 if (nb_rooms != NULL)
2792 *nb_rooms = _nb_rooms;
2793 if (nb_private != NULL)
2794 *nb_private = _nb_private;