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)) \
72 PROP_INDIVIDUAL_MGR = 1
75 struct _EmpathyChatWindowPriv
77 EmpathyChat *current_chat;
80 gboolean dnd_same_window;
81 EmpathyChatroomManager *chatroom_manager;
82 EmpathyNotifyManager *notify_mgr;
83 EmpathyIndividualManager *individual_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;
121 gboolean updating_menu;
124 static GList *chat_windows = NULL;
126 static const guint tab_accel_keys[] =
128 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
129 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
134 DND_DRAG_TYPE_CONTACT_ID,
135 DND_DRAG_TYPE_INDIVIDUAL_ID,
136 DND_DRAG_TYPE_URI_LIST,
140 static const GtkTargetEntry drag_types_dest[] =
142 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
143 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
144 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
145 /* FIXME: disabled because of bug #640513
146 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
147 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
151 static const GtkTargetEntry drag_types_dest_contact[] =
153 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
154 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
157 static const GtkTargetEntry drag_types_dest_file[] =
159 /* must be first to be prioritized, in order to receive the
160 * note's file path from Tomboy instead of an URI */
161 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
162 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
165 static void chat_window_update (EmpathyChatWindow *window,
166 gboolean update_contact_menu);
168 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
171 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
174 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
175 EmpathyChatWindow *new_window,
178 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
182 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_WINDOW)
185 chat_window_accel_cb (GtkAccelGroup *accelgroup,
189 EmpathyChatWindow *self)
194 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
196 if (tab_accel_keys[i] == key)
204 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
207 static EmpathyChatWindow *
208 chat_window_find_chat (EmpathyChat *chat)
212 for (l = chat_windows; l; l = l->next)
214 EmpathyChatWindow *window = l->data;
216 ll = g_list_find (window->priv->chats, chat);
225 remove_all_chats (EmpathyChatWindow *self)
229 while (self->priv->chats)
230 empathy_chat_window_remove_chat (self, self->priv->chats->data);
232 g_object_unref (self);
236 confirm_close_response_cb (GtkWidget *dialog,
238 EmpathyChatWindow *window)
242 chat = g_object_get_data (G_OBJECT (dialog), "chat");
244 gtk_widget_destroy (dialog);
246 if (response != GTK_RESPONSE_ACCEPT)
250 empathy_chat_window_remove_chat (window, chat);
252 remove_all_chats (window);
256 confirm_close (EmpathyChatWindow *self,
257 gboolean close_window,
262 gchar *primary, *secondary;
264 g_return_if_fail (n_rooms > 0);
267 g_return_if_fail (chat == NULL);
269 g_return_if_fail (chat != NULL);
271 /* If there are no chats in this window, how could we possibly have got
274 g_return_if_fail (self->priv->chats != NULL);
276 /* Treat closing a window which only has one tab exactly like closing
279 if (close_window && self->priv->chats->next == NULL)
281 close_window = FALSE;
282 chat = self->priv->chats->data;
287 primary = g_strdup (_("Close this window?"));
291 gchar *chat_name = empathy_chat_dup_name (chat);
292 secondary = g_strdup_printf (
293 _("Closing this window will leave %s. You will "
294 "not receive any further messages until you "
301 secondary = g_strdup_printf (
302 /* Note to translators: the number of chats will
303 * always be at least 2.
306 "Closing this window will leave a chat room. You will "
307 "not receive any further messages until you rejoin it.",
308 "Closing this window will leave %u chat rooms. You will "
309 "not receive any further messages until you rejoin them.",
316 gchar *chat_name = empathy_chat_dup_name (chat);
317 primary = g_strdup_printf (_("Leave %s?"), chat_name);
318 secondary = g_strdup (
319 _("You will not receive any further messages from this chat "
320 "room until you rejoin it."));
324 dialog = gtk_message_dialog_new (
326 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
331 gtk_window_set_title (GTK_WINDOW (dialog), "");
332 g_object_set (dialog, "secondary-text", secondary, NULL);
337 gtk_dialog_add_button (GTK_DIALOG (dialog),
338 close_window ? _("Close window") : _("Leave room"),
339 GTK_RESPONSE_ACCEPT);
340 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
341 GTK_RESPONSE_ACCEPT);
344 g_object_set_data (G_OBJECT (dialog), "chat", chat);
346 g_signal_connect (dialog, "response",
347 G_CALLBACK (confirm_close_response_cb), self);
349 gtk_window_present (GTK_WINDOW (dialog));
352 /* Returns TRUE if we should check if the user really wants to leave. If it's
353 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
354 * the user is actually in the room as opposed to having been kicked or gone
355 * offline or something), then we should check.
358 chat_needs_close_confirmation (EmpathyChat *chat)
360 return (empathy_chat_is_room (chat) &&
361 empathy_chat_get_tp_chat (chat) != NULL);
365 maybe_close_chat (EmpathyChatWindow *window,
368 g_return_if_fail (chat != NULL);
370 if (chat_needs_close_confirmation (chat))
371 confirm_close (window, FALSE, 1, chat);
373 empathy_chat_window_remove_chat (window, chat);
377 chat_window_close_clicked_cb (GtkAction *action,
380 EmpathyChatWindow *window;
382 window = chat_window_find_chat (chat);
383 maybe_close_chat (window, chat);
387 chat_tab_style_updated_cb (GtkWidget *hbox,
391 int char_width, h, w;
392 PangoContext *context;
393 const PangoFontDescription *font_desc;
394 PangoFontMetrics *metrics;
396 button = g_object_get_data (G_OBJECT (user_data),
397 "chat-window-tab-close-button");
398 context = gtk_widget_get_pango_context (hbox);
400 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
401 GTK_STATE_FLAG_NORMAL);
403 metrics = pango_context_get_metrics (context, font_desc,
404 pango_context_get_language (context));
405 char_width = pango_font_metrics_get_approximate_char_width (metrics);
406 pango_font_metrics_unref (metrics);
408 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
409 GTK_ICON_SIZE_MENU, &w, &h);
411 /* Request at least about 12 chars width plus at least space for the status
412 * image and the close button */
413 gtk_widget_set_size_request (hbox,
414 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
416 gtk_widget_set_size_request (button, w, h);
420 create_close_button (void)
422 GtkWidget *button, *image;
423 GtkStyleContext *context;
425 button = gtk_button_new ();
427 context = gtk_widget_get_style_context (button);
428 gtk_style_context_add_class (context, "empathy-tab-close-button");
430 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
431 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
433 /* We don't want focus/keynav for the button to avoid clutter, and
434 * Ctrl-W works anyway.
436 gtk_widget_set_can_focus (button, FALSE);
437 gtk_widget_set_can_default (button, FALSE);
439 image = gtk_image_new_from_icon_name ("window-close-symbolic",
441 gtk_widget_show (image);
443 gtk_container_add (GTK_CONTAINER (button), image);
449 chat_window_create_label (EmpathyChatWindow *window,
451 gboolean is_tab_label)
454 GtkWidget *name_label;
455 GtkWidget *status_image;
456 GtkWidget *event_box;
457 GtkWidget *event_box_hbox;
458 PangoAttrList *attr_list;
459 PangoAttribute *attr;
461 /* The spacing between the button and the label. */
462 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
464 event_box = gtk_event_box_new ();
465 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
467 name_label = gtk_label_new (NULL);
469 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
471 attr_list = pango_attr_list_new ();
472 attr = pango_attr_scale_new (1/1.2);
473 attr->start_index = 0;
474 attr->end_index = -1;
475 pango_attr_list_insert (attr_list, attr);
476 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
477 pango_attr_list_unref (attr_list);
479 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
480 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
481 g_object_set_data (G_OBJECT (chat),
482 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
485 status_image = gtk_image_new ();
487 /* Spacing between the icon and label. */
488 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
490 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
491 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
493 g_object_set_data (G_OBJECT (chat),
494 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
496 g_object_set_data (G_OBJECT (chat),
497 is_tab_label ? "chat-window-tab-tooltip-widget" :
498 "chat-window-menu-tooltip-widget",
501 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
502 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
506 GtkWidget *close_button;
507 GtkWidget *sending_spinner;
509 sending_spinner = gtk_spinner_new ();
511 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
513 g_object_set_data (G_OBJECT (chat),
514 "chat-window-tab-sending-spinner",
517 close_button = create_close_button ();
518 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
521 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
523 g_signal_connect (close_button,
525 G_CALLBACK (chat_window_close_clicked_cb), chat);
527 /* React to theme changes and also setup the size correctly. */
528 g_signal_connect (hbox, "style-updated",
529 G_CALLBACK (chat_tab_style_updated_cb), chat);
532 gtk_widget_show_all (hbox);
538 _submenu_notify_visible_changed_cb (GObject *object,
542 g_signal_handlers_disconnect_by_func (object,
543 _submenu_notify_visible_changed_cb, userdata);
545 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
549 chat_window_menu_context_update (EmpathyChatWindow *self,
554 gboolean wrap_around;
555 gboolean is_connected;
558 page_num = gtk_notebook_get_current_page (
559 GTK_NOTEBOOK (self->priv->notebook));
560 first_page = (page_num == 0);
561 last_page = (page_num == (num_pages - 1));
562 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
564 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
566 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
568 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
570 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
571 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
572 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
573 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
577 chat_window_conversation_menu_update (EmpathyChatWindow *self)
579 EmpathyTpChat *tp_chat;
580 TpConnection *connection;
582 gboolean sensitive = FALSE;
584 g_return_if_fail (self->priv->current_chat != NULL);
586 action = gtk_ui_manager_get_action (self->priv->ui_manager,
587 "/chats_menubar/menu_conv/menu_conv_invite_participant");
588 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
592 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
594 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
595 (tp_connection_get_status (connection, NULL) ==
596 TP_CONNECTION_STATUS_CONNECTED);
599 gtk_action_set_sensitive (action, sensitive);
603 chat_window_contact_menu_update (EmpathyChatWindow *self)
605 GtkWidget *menu, *submenu, *orig_submenu;
607 if (self->priv->updating_menu)
609 self->priv->updating_menu = TRUE;
611 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
612 "/chats_menubar/menu_contact");
613 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
615 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
617 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
621 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
622 g_object_set_data (G_OBJECT (submenu), "window", self);
624 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
625 gtk_widget_show (menu);
626 gtk_widget_set_sensitive (menu, TRUE);
630 gtk_widget_set_sensitive (menu, FALSE);
635 tp_g_signal_connect_object (orig_submenu,
637 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
640 self->priv->updating_menu = FALSE;
644 get_all_unread_messages (EmpathyChatWindow *self)
649 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
650 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
656 get_window_title_name (EmpathyChatWindow *self)
658 gchar *active_name, *ret;
660 guint current_unread_msgs;
662 nb_chats = g_list_length (self->priv->chats);
663 g_assert (nb_chats > 0);
665 active_name = empathy_chat_dup_name (self->priv->current_chat);
667 current_unread_msgs = empathy_chat_get_nb_unread_messages (
668 self->priv->current_chat);
673 if (current_unread_msgs == 0)
674 ret = g_strdup (active_name);
676 ret = g_strdup_printf (ngettext (
678 "%s (%d unread)", current_unread_msgs),
679 active_name, current_unread_msgs);
683 guint nb_others = nb_chats - 1;
684 guint all_unread_msgs;
686 all_unread_msgs = get_all_unread_messages (self);
688 if (all_unread_msgs == 0)
690 /* no unread message */
691 ret = g_strdup_printf (ngettext (
693 "%s (and %u others)", nb_others),
694 active_name, nb_others);
696 else if (all_unread_msgs == current_unread_msgs)
698 /* unread messages are in the current tab */
699 ret = g_strdup_printf (ngettext (
701 "%s (%d unread)", current_unread_msgs),
702 active_name, current_unread_msgs);
704 else if (current_unread_msgs == 0)
706 /* unread messages are in other tabs */
707 ret = g_strdup_printf (ngettext (
708 "%s (%d unread from others)",
709 "%s (%d unread from others)",
711 active_name, all_unread_msgs);
715 /* unread messages are in all the tabs */
716 ret = g_strdup_printf (ngettext (
717 "%s (%d unread from all)",
718 "%s (%d unread from all)",
720 active_name, all_unread_msgs);
724 g_free (active_name);
730 chat_window_title_update (EmpathyChatWindow *self)
734 name = get_window_title_name (self);
735 gtk_window_set_title (GTK_WINDOW (self), name);
740 chat_window_icon_update (EmpathyChatWindow *self,
741 gboolean new_messages)
744 EmpathyContact *remote_contact;
745 gboolean avatar_in_icon;
748 n_chats = g_list_length (self->priv->chats);
750 /* Update window icon */
753 gtk_window_set_icon_name (GTK_WINDOW (self),
754 EMPATHY_IMAGE_MESSAGE);
758 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
759 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
761 if (n_chats == 1 && avatar_in_icon)
763 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
764 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
766 gtk_window_set_icon (GTK_WINDOW (self), icon);
769 g_object_unref (icon);
773 gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
779 chat_window_close_button_update (EmpathyChatWindow *self,
783 GtkWidget *chat_close_button;
788 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
789 chat_close_button = g_object_get_data (G_OBJECT (chat),
790 "chat-window-tab-close-button");
791 gtk_widget_hide (chat_close_button);
795 for (i=0; i<num_pages; i++)
797 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
798 chat_close_button = g_object_get_data (G_OBJECT (chat),
799 "chat-window-tab-close-button");
800 gtk_widget_show (chat_close_button);
806 chat_window_update (EmpathyChatWindow *self,
807 gboolean update_contact_menu)
811 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
813 /* Update Tab menu */
814 chat_window_menu_context_update (self, num_pages);
816 chat_window_conversation_menu_update (self);
818 /* If this update is due to a focus-in event, we know the menu will be
819 the same as when we last left it, so no work to do. Besides, if we
820 swap out the menu on a focus-in, we may confuse any external global
822 if (update_contact_menu)
824 chat_window_contact_menu_update (self);
827 chat_window_title_update (self);
829 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
831 chat_window_close_button_update (self, num_pages);
835 append_markup_printf (GString *string,
842 va_start (args, format);
844 tmp = g_markup_vprintf_escaped (format, args);
845 g_string_append (string, tmp);
852 chat_window_update_chat_tab_full (EmpathyChat *chat,
853 gboolean update_contact_menu)
855 EmpathyChatWindow *self;
856 EmpathyContact *remote_contact;
860 const gchar *subject;
861 const gchar *status = NULL;
865 const gchar *icon_name;
866 GtkWidget *tab_image;
867 GtkWidget *menu_image;
868 GtkWidget *sending_spinner;
871 self = chat_window_find_chat (chat);
875 /* Get information */
876 name = empathy_chat_dup_name (chat);
877 account = empathy_chat_get_account (chat);
878 subject = empathy_chat_get_subject (chat);
879 remote_contact = empathy_chat_get_remote_contact (chat);
881 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
883 name, tp_proxy_get_object_path (account), subject, remote_contact);
885 /* Update tab image */
886 if (empathy_chat_get_tp_chat (chat) == NULL)
888 /* No TpChat, we are disconnected */
891 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
893 icon_name = EMPATHY_IMAGE_MESSAGE;
895 else if (remote_contact && empathy_chat_is_composing (chat))
897 icon_name = EMPATHY_IMAGE_TYPING;
899 else if (empathy_chat_is_sms_channel (chat))
901 icon_name = EMPATHY_IMAGE_SMS;
903 else if (remote_contact)
905 icon_name = empathy_icon_name_for_contact (remote_contact);
909 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
912 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
913 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
915 if (icon_name != NULL)
917 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
919 gtk_widget_show (tab_image);
920 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
922 gtk_widget_show (menu_image);
926 gtk_widget_hide (tab_image);
927 gtk_widget_hide (menu_image);
930 /* Update the sending spinner */
931 nb_sending = empathy_chat_get_n_messages_sending (chat);
932 sending_spinner = g_object_get_data (G_OBJECT (chat),
933 "chat-window-tab-sending-spinner");
935 g_object_set (sending_spinner,
936 "active", nb_sending > 0,
937 "visible", nb_sending > 0,
940 /* Update tab tooltip */
941 tooltip = g_string_new (NULL);
945 id = empathy_contact_get_id (remote_contact);
946 status = empathy_contact_get_presence_message (remote_contact);
953 if (empathy_chat_is_sms_channel (chat))
954 append_markup_printf (tooltip, "%s ", _("SMS:"));
956 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
957 id, tp_account_get_display_name (account));
961 char *tmp = g_strdup_printf (
962 ngettext ("Sending %d message",
963 "Sending %d messages",
967 g_string_append (tooltip, "\n");
968 g_string_append (tooltip, tmp);
970 gtk_widget_set_tooltip_text (sending_spinner, tmp);
974 if (!EMP_STR_EMPTY (status))
975 append_markup_printf (tooltip, "\n<i>%s</i>", status);
977 if (!EMP_STR_EMPTY (subject))
978 append_markup_printf (tooltip, "\n<b>%s</b> %s",
979 _("Topic:"), subject);
981 if (remote_contact && empathy_chat_is_composing (chat))
982 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
984 if (remote_contact != NULL)
986 const gchar * const *types;
988 types = empathy_contact_get_client_types (remote_contact);
989 if (empathy_client_types_contains_mobile_device ((GStrv) types))
991 /* I'm on a mobile device ! */
994 name = g_strdup_printf ("☎ %s", name);
999 markup = g_string_free (tooltip, FALSE);
1000 widget = g_object_get_data (G_OBJECT (chat),
1001 "chat-window-tab-tooltip-widget");
1002 gtk_widget_set_tooltip_markup (widget, markup);
1004 widget = g_object_get_data (G_OBJECT (chat),
1005 "chat-window-menu-tooltip-widget");
1006 gtk_widget_set_tooltip_markup (widget, markup);
1009 /* Update tab and menu label */
1010 if (empathy_chat_is_highlighted (chat))
1012 markup = g_markup_printf_escaped (
1013 "<span color=\"red\" weight=\"bold\">%s</span>",
1018 markup = g_markup_escape_text (name, -1);
1021 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1022 gtk_label_set_markup (GTK_LABEL (widget), markup);
1023 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1024 gtk_label_set_markup (GTK_LABEL (widget), markup);
1027 /* Update the window if it's the current chat */
1028 if (self->priv->current_chat == chat)
1029 chat_window_update (self, update_contact_menu);
1035 chat_window_update_chat_tab (EmpathyChat *chat)
1037 chat_window_update_chat_tab_full (chat, TRUE);
1041 chat_window_chat_notify_cb (EmpathyChat *chat)
1043 EmpathyChatWindow *window;
1044 EmpathyContact *old_remote_contact;
1045 EmpathyContact *remote_contact = NULL;
1047 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1048 "chat-window-remote-contact");
1049 remote_contact = empathy_chat_get_remote_contact (chat);
1051 if (old_remote_contact != remote_contact)
1053 /* The remote-contact associated with the chat changed, we need
1054 * to keep track of any change of that contact and update the
1055 * window each time. */
1057 g_signal_connect_swapped (remote_contact, "notify",
1058 G_CALLBACK (chat_window_update_chat_tab), chat);
1060 if (old_remote_contact)
1061 g_signal_handlers_disconnect_by_func (old_remote_contact,
1062 chat_window_update_chat_tab, chat);
1064 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1065 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1068 chat_window_update_chat_tab (chat);
1070 window = chat_window_find_chat (chat);
1072 chat_window_update (window, FALSE);
1076 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1077 EmpathySmiley *smiley,
1080 EmpathyChatWindow *self = user_data;
1082 GtkTextBuffer *buffer;
1085 chat = self->priv->current_chat;
1087 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1088 gtk_text_buffer_get_end_iter (buffer, &iter);
1089 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1093 chat_window_conv_activate_cb (GtkAction *action,
1094 EmpathyChatWindow *self)
1098 EmpathyContact *remote_contact = NULL;
1100 /* Favorite room menu */
1101 is_room = empathy_chat_is_room (self->priv->current_chat);
1106 gboolean found = FALSE;
1107 EmpathyChatroom *chatroom;
1109 room = empathy_chat_get_id (self->priv->current_chat);
1110 account = empathy_chat_get_account (self->priv->current_chat);
1111 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1114 if (chatroom != NULL)
1115 found = empathy_chatroom_is_favorite (chatroom);
1117 DEBUG ("This room %s favorite", found ? "is" : "is not");
1118 gtk_toggle_action_set_active (
1119 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1121 if (chatroom != NULL)
1122 found = empathy_chatroom_is_always_urgent (chatroom);
1124 gtk_toggle_action_set_active (
1125 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1128 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1129 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1131 /* Show contacts menu */
1132 g_object_get (self->priv->current_chat,
1133 "remote-contact", &remote_contact,
1134 "show-contacts", &active,
1137 if (remote_contact == NULL)
1139 gtk_toggle_action_set_active (
1140 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1143 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1144 (remote_contact == NULL));
1146 if (remote_contact != NULL)
1147 g_object_unref (remote_contact);
1151 chat_window_clear_activate_cb (GtkAction *action,
1152 EmpathyChatWindow *self)
1154 empathy_chat_clear (self->priv->current_chat);
1158 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1159 EmpathyChatWindow *self)
1165 EmpathyChatroom *chatroom;
1167 active = gtk_toggle_action_get_active (toggle_action);
1168 account = empathy_chat_get_account (self->priv->current_chat);
1169 room = empathy_chat_get_id (self->priv->current_chat);
1170 name = empathy_chat_dup_name (self->priv->current_chat);
1172 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1173 account, room, name);
1175 empathy_chatroom_set_favorite (chatroom, active);
1176 g_object_unref (chatroom);
1181 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1182 EmpathyChatWindow *self)
1188 EmpathyChatroom *chatroom;
1190 active = gtk_toggle_action_get_active (toggle_action);
1191 account = empathy_chat_get_account (self->priv->current_chat);
1192 room = empathy_chat_get_id (self->priv->current_chat);
1193 name = empathy_chat_dup_name (self->priv->current_chat);
1195 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1196 account, room, name);
1198 empathy_chatroom_set_always_urgent (chatroom, active);
1199 g_object_unref (chatroom);
1204 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1205 EmpathyChatWindow *self)
1209 active = gtk_toggle_action_get_active (toggle_action);
1211 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1215 chat_window_invite_participant_activate_cb (GtkAction *action,
1216 EmpathyChatWindow *self)
1219 EmpathyTpChat *tp_chat;
1222 g_return_if_fail (self->priv->current_chat != NULL);
1224 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1226 dialog = empathy_invite_participant_dialog_new (
1227 GTK_WINDOW (self), tp_chat);
1229 gtk_widget_show (dialog);
1231 response = gtk_dialog_run (GTK_DIALOG (dialog));
1233 if (response == GTK_RESPONSE_ACCEPT)
1235 TpContact *tp_contact;
1236 EmpathyContact *contact;
1238 tp_contact = empathy_invite_participant_dialog_get_selected (
1239 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1240 if (tp_contact == NULL)
1243 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1245 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1247 g_object_unref (contact);
1251 gtk_widget_destroy (dialog);
1255 chat_window_close_activate_cb (GtkAction *action,
1256 EmpathyChatWindow *self)
1258 g_return_if_fail (self->priv->current_chat != NULL);
1260 maybe_close_chat (self, self->priv->current_chat);
1264 chat_window_edit_activate_cb (GtkAction *action,
1265 EmpathyChatWindow *self)
1267 GtkClipboard *clipboard;
1268 GtkTextBuffer *buffer;
1269 gboolean text_available;
1271 g_return_if_fail (self->priv->current_chat != NULL);
1273 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1275 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1276 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1277 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1281 buffer = gtk_text_view_get_buffer (
1282 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1284 if (gtk_text_buffer_get_has_selection (buffer))
1286 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1287 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1293 selection = empathy_theme_adium_get_has_selection (
1294 self->priv->current_chat->view);
1296 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1297 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1300 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1301 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1302 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1306 chat_window_cut_activate_cb (GtkAction *action,
1307 EmpathyChatWindow *self)
1309 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1311 empathy_chat_cut (self->priv->current_chat);
1315 chat_window_copy_activate_cb (GtkAction *action,
1316 EmpathyChatWindow *self)
1318 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1320 empathy_chat_copy (self->priv->current_chat);
1324 chat_window_paste_activate_cb (GtkAction *action,
1325 EmpathyChatWindow *self)
1327 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1329 empathy_chat_paste (self->priv->current_chat);
1333 chat_window_find_activate_cb (GtkAction *action,
1334 EmpathyChatWindow *self)
1336 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1338 empathy_chat_find (self->priv->current_chat);
1342 chat_window_tabs_next_activate_cb (GtkAction *action,
1343 EmpathyChatWindow *self)
1345 gint index_, numPages;
1346 gboolean wrap_around;
1348 g_object_get (gtk_settings_get_default (),
1349 "gtk-keynav-wrap-around", &wrap_around,
1352 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1353 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1355 if (index_ == (numPages - 1) && wrap_around)
1357 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1361 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1365 chat_window_tabs_previous_activate_cb (GtkAction *action,
1366 EmpathyChatWindow *self)
1368 gint index_, numPages;
1369 gboolean wrap_around;
1371 g_object_get (gtk_settings_get_default (),
1372 "gtk-keynav-wrap-around", &wrap_around,
1375 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1376 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1378 if (index_ <= 0 && wrap_around)
1380 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1385 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1389 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1390 EmpathyChatWindow *self)
1392 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1393 empathy_get_current_action_time ());
1397 chat_window_tabs_left_activate_cb (GtkAction *action,
1398 EmpathyChatWindow *self)
1401 gint index_, num_pages;
1403 chat = self->priv->current_chat;
1404 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1408 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1411 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1412 chat_window_menu_context_update (self, num_pages);
1416 chat_window_tabs_right_activate_cb (GtkAction *action,
1417 EmpathyChatWindow *self)
1420 gint index_, num_pages;
1422 chat = self->priv->current_chat;
1423 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1425 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1428 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1429 chat_window_menu_context_update (self, num_pages);
1432 static EmpathyChatWindow *
1433 empathy_chat_window_new (void)
1435 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1436 "default-width", 580,
1437 "default-height", 480,
1444 chat_window_detach_activate_cb (GtkAction *action,
1445 EmpathyChatWindow *self)
1447 EmpathyChatWindow *new_window;
1450 chat = self->priv->current_chat;
1451 new_window = empathy_chat_window_new ();
1453 empathy_chat_window_move_chat (self, new_window, chat);
1455 gtk_widget_show (GTK_WIDGET (new_window));
1459 chat_window_help_contents_activate_cb (GtkAction *action,
1460 EmpathyChatWindow *self)
1462 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1466 chat_window_help_about_activate_cb (GtkAction *action,
1467 EmpathyChatWindow *self)
1469 empathy_about_dialog_new (GTK_WINDOW (self));
1473 chat_window_delete_event_cb (GtkWidget *dialog,
1475 EmpathyChatWindow *self)
1477 EmpathyChat *chat = NULL;
1481 DEBUG ("Delete event received");
1483 for (l = self->priv->chats; l != NULL; l = l->next)
1485 if (chat_needs_close_confirmation (l->data))
1494 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1498 remove_all_chats (self);
1505 chat_window_composing_cb (EmpathyChat *chat,
1506 gboolean is_composing,
1507 EmpathyChatWindow *self)
1509 chat_window_update_chat_tab (chat);
1513 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1516 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1520 chat_window_notification_closed_cb (NotifyNotification *notify,
1521 EmpathyChatWindow *self)
1523 g_object_unref (notify);
1524 if (self->priv->notification == notify)
1525 self->priv->notification = NULL;
1529 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1530 EmpathyMessage *message,
1533 EmpathyContact *sender;
1534 const gchar *header;
1538 gboolean res, has_x_canonical_append;
1539 NotifyNotification *notification = self->priv->notification;
1541 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1544 res = g_settings_get_boolean (self->priv->gsettings_notif,
1545 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1550 sender = empathy_message_get_sender (message);
1551 header = empathy_contact_get_alias (sender);
1552 body = empathy_message_get_body (message);
1553 escaped = g_markup_escape_text (body, -1);
1555 has_x_canonical_append = empathy_notify_manager_has_capability (
1556 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1558 if (notification != NULL && !has_x_canonical_append)
1560 /* if the notification server supports x-canonical-append, it is
1561 better to not use notify_notification_update to avoid
1562 overwriting the current notification message */
1563 notify_notification_update (notification,
1564 header, escaped, NULL);
1568 /* if the notification server supports x-canonical-append,
1569 the hint will be added, so that the message from the
1570 just created notification will be automatically appended
1571 to an existing notification with the same title.
1572 In this way the previous message will not be lost: the new
1573 message will appear below it, in the same notification */
1574 const gchar *category = empathy_chat_is_room (chat)
1575 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1576 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1578 notification = empathy_notify_manager_create_notification (header,
1581 if (self->priv->notification == NULL)
1582 self->priv->notification = notification;
1584 tp_g_signal_connect_object (notification, "closed",
1585 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1587 if (has_x_canonical_append)
1589 /* We have to set a not empty string to keep libnotify happy */
1590 notify_notification_set_hint_string (notification,
1591 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1594 notify_notification_set_hint (notification,
1595 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1598 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1599 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1603 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1604 g_object_unref (pixbuf);
1607 notify_notification_show (notification, NULL);
1613 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1617 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1619 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1625 chat_window_new_message_cb (EmpathyChat *chat,
1626 EmpathyMessage *message,
1628 gboolean should_highlight,
1629 EmpathyChatWindow *self)
1632 gboolean needs_urgency;
1633 EmpathyContact *sender;
1635 has_focus = empathy_chat_window_has_focus (self);
1637 /* - if we're the sender, we play the sound if it's specified in the
1638 * preferences and we're not away.
1639 * - if we receive a message, we play the sound if it's specified in the
1640 * preferences and the window does not have focus on the chat receiving
1644 sender = empathy_message_get_sender (message);
1646 if (empathy_contact_is_user (sender))
1648 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1649 EMPATHY_SOUND_MESSAGE_OUTGOING);
1653 if (has_focus && self->priv->current_chat == chat)
1655 /* window and tab are focused so consider the message to be read */
1657 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1658 empathy_chat_messages_read (chat);
1662 /* Update the chat tab if this is the first unread message */
1663 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1665 chat_window_update_chat_tab (chat);
1668 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1669 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1670 * an unamed MUC (msn-like).
1671 * In case of a MUC, we set urgency if either:
1672 * a) the chatroom's always_urgent property is TRUE
1673 * b) the message contains our alias
1675 if (empathy_chat_is_room (chat))
1679 EmpathyChatroom *chatroom;
1681 account = empathy_chat_get_account (chat);
1682 room = empathy_chat_get_id (chat);
1684 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1687 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1688 needs_urgency = TRUE;
1690 needs_urgency = should_highlight;
1694 needs_urgency = TRUE;
1700 chat_window_set_urgency_hint (self, TRUE);
1702 /* Pending messages have already been displayed and notified in the
1703 * approver, so we don't display a notification and play a sound
1707 empathy_sound_manager_play (self->priv->sound_mgr,
1708 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1710 chat_window_show_or_update_notification (self, message, chat);
1714 /* update the number of unread messages and the window icon */
1715 chat_window_title_update (self);
1716 chat_window_icon_update (self, TRUE);
1720 chat_window_command_part (EmpathyChat *chat,
1723 EmpathyChat *chat_to_be_parted;
1724 EmpathyTpChat *tp_chat = NULL;
1726 if (strv[1] == NULL)
1728 /* No chatroom ID specified */
1729 tp_chat = empathy_chat_get_tp_chat (chat);
1732 empathy_tp_chat_leave (tp_chat, "");
1737 chat_to_be_parted = empathy_chat_window_find_chat (
1738 empathy_chat_get_account (chat), strv[1], FALSE);
1740 if (chat_to_be_parted != NULL)
1742 /* Found a chatroom matching the specified ID */
1743 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1746 empathy_tp_chat_leave (tp_chat, strv[2]);
1752 /* Going by the syntax of PART command:
1754 * /PART [<chatroom-ID>] [<reason>]
1756 * Chatroom-ID is not a must to specify a reason.
1757 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1758 * MUC then the current chatroom should be parted and srtv[1] should
1759 * be treated as part of the optional part-message. */
1760 message = g_strconcat (strv[1], " ", strv[2], NULL);
1761 tp_chat = empathy_chat_get_tp_chat (chat);
1764 empathy_tp_chat_leave (tp_chat, message);
1770 static GtkNotebook *
1771 notebook_create_window_cb (GtkNotebook *source,
1777 EmpathyChatWindow *window, *new_window;
1780 chat = EMPATHY_CHAT (page);
1781 window = chat_window_find_chat (chat);
1783 new_window = empathy_chat_window_new ();
1785 DEBUG ("Detach hook called");
1787 empathy_chat_window_move_chat (window, new_window, chat);
1789 gtk_widget_show (GTK_WIDGET (new_window));
1790 gtk_window_move (GTK_WINDOW (new_window), x, y);
1796 chat_window_page_switched_cb (GtkNotebook *notebook,
1799 EmpathyChatWindow *self)
1801 EmpathyChat *chat = EMPATHY_CHAT (child);
1803 DEBUG ("Page switched");
1805 if (self->priv->page_added)
1807 self->priv->page_added = FALSE;
1808 empathy_chat_scroll_down (chat);
1810 else if (self->priv->current_chat == chat)
1815 self->priv->current_chat = chat;
1816 empathy_chat_messages_read (chat);
1818 chat_window_update_chat_tab (chat);
1822 chat_window_page_added_cb (GtkNotebook *notebook,
1825 EmpathyChatWindow *self)
1829 /* If we just received DND to the same window, we don't want
1830 * to do anything here like removing the tab and then readding
1831 * it, so we return here and in "page-added".
1833 if (self->priv->dnd_same_window)
1835 DEBUG ("Page added (back to the same window)");
1836 self->priv->dnd_same_window = FALSE;
1840 DEBUG ("Page added");
1842 /* Get chat object */
1843 chat = EMPATHY_CHAT (child);
1845 /* Connect chat signals for this window */
1846 g_signal_connect (chat, "composing",
1847 G_CALLBACK (chat_window_composing_cb), self);
1848 g_signal_connect (chat, "new-message",
1849 G_CALLBACK (chat_window_new_message_cb), self);
1850 g_signal_connect (chat, "part-command-entered",
1851 G_CALLBACK (chat_window_command_part), NULL);
1852 g_signal_connect (chat, "notify::tp-chat",
1853 G_CALLBACK (chat_window_update_chat_tab), self);
1855 /* Set flag so we know to perform some special operations on
1856 * switch page due to the new page being added.
1858 self->priv->page_added = TRUE;
1860 /* Get list of chats up to date */
1861 self->priv->chats = g_list_append (self->priv->chats, chat);
1863 chat_window_update_chat_tab (chat);
1867 chat_window_page_removed_cb (GtkNotebook *notebook,
1870 EmpathyChatWindow *self)
1874 /* If we just received DND to the same window, we don't want
1875 * to do anything here like removing the tab and then readding
1876 * it, so we return here and in "page-added".
1878 if (self->priv->dnd_same_window)
1880 DEBUG ("Page removed (and will be readded to same window)");
1884 DEBUG ("Page removed");
1886 /* Get chat object */
1887 chat = EMPATHY_CHAT (child);
1889 /* Disconnect all signal handlers for this chat and this window */
1890 g_signal_handlers_disconnect_by_func (chat,
1891 G_CALLBACK (chat_window_composing_cb), self);
1892 g_signal_handlers_disconnect_by_func (chat,
1893 G_CALLBACK (chat_window_new_message_cb), self);
1894 g_signal_handlers_disconnect_by_func (chat,
1895 G_CALLBACK (chat_window_update_chat_tab), self);
1897 /* Keep list of chats up to date */
1898 self->priv->chats = g_list_remove (self->priv->chats, chat);
1899 empathy_chat_messages_read (chat);
1901 if (self->priv->chats == NULL)
1903 gtk_widget_destroy (GTK_WIDGET (self));
1907 chat_window_update (self, TRUE);
1912 chat_window_focus_in_event_cb (GtkWidget *widget,
1914 EmpathyChatWindow *self)
1916 empathy_chat_messages_read (self->priv->current_chat);
1918 chat_window_set_urgency_hint (self, FALSE);
1920 /* Update the title, since we now mark all unread messages as read. */
1921 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1927 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1928 EmpathyChatWindow *self)
1930 chat_window_contact_menu_update (self);
1934 chat_window_focus_out_event_cb (GtkWidget *widget,
1936 EmpathyChatWindow *self)
1938 if (self->priv->individual_mgr != NULL)
1941 /* Keep the individual manager alive so we won't fetch everything from Folks
1942 * each time we need to use it. Loading FolksAggregator can takes quite a
1943 * while (if user has a huge LDAP abook for example) and it blocks
1944 * the mainloop during most of this loading. We workaround this by loading
1945 * it when the chat window has been unfocused and so, hopefully, not impact
1946 * the reactivity of the chat window too much.
1948 * The individual manager (and so Folks) is needed to know to which
1949 * FolksIndividual a TpContact belongs, including:
1950 * - empathy_chat_get_contact_menu: to list all the personas of the contact
1951 * - empathy_display_individual_info: to invoke gnome-contacts with the
1952 * FolksIndividual.id of the contact
1953 * - drag_data_received_individual_id: to find the individual associated
1954 * with the ID we received from the DnD in order to invite him.
1956 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
1958 if (!empathy_individual_manager_get_contacts_loaded (
1959 self->priv->individual_mgr))
1961 /* We want to update the contact menu when Folks is loaded so we can
1962 * list all the personas of the contact. */
1963 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
1964 G_CALLBACK (contacts_loaded_cb), self, 0);
1967 g_object_notify (G_OBJECT (self), "individual-manager");
1973 chat_window_drag_drop (GtkWidget *widget,
1974 GdkDragContext *context,
1978 EmpathyChatWindow *self)
1982 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1983 if (target == GDK_NONE)
1984 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1986 if (target != GDK_NONE)
1988 gtk_drag_get_data (widget, context, target, time_);
1996 chat_window_drag_motion (GtkWidget *widget,
1997 GdkDragContext *context,
2001 EmpathyChatWindow *self)
2005 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2007 if (target != GDK_NONE)
2009 /* This is a file drag. Ensure the contact is online and set the
2010 drag type to COPY. Note that it's possible that the tab will
2011 be switched by GTK+ after a timeout from drag_motion without
2012 getting another drag_motion to disable the drop. You have
2013 to hold your mouse really still.
2015 EmpathyContact *contact;
2017 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2019 /* contact is NULL for multi-user chats. We don't do
2020 * file transfers to MUCs. We also don't send files
2021 * to offline contacts or contacts that don't support
2024 if ((contact == NULL) || !empathy_contact_is_online (contact))
2026 gdk_drag_status (context, 0, time_);
2030 if (!(empathy_contact_get_capabilities (contact)
2031 & EMPATHY_CAPABILITIES_FT))
2033 gdk_drag_status (context, 0, time_);
2037 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2041 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2042 if (target != GDK_NONE)
2044 /* This is a drag of a contact from a contact list. Set to COPY.
2045 FIXME: If this drag is to a MUC window, it invites the user.
2046 Otherwise, it opens a chat. Should we use a different drag
2047 type for invites? Should we allow ASK?
2049 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2057 drag_data_received_individual_id (EmpathyChatWindow *self,
2059 GdkDragContext *context,
2062 GtkSelectionData *selection,
2067 FolksIndividual *individual;
2068 EmpathyTpChat *chat;
2069 TpContact *tp_contact;
2071 EmpathyContact *contact;
2073 id = (const gchar *) gtk_selection_data_get_data (selection);
2075 DEBUG ("DND invididual %s", id);
2077 if (self->priv->current_chat == NULL)
2080 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2084 if (!empathy_tp_chat_can_add_contact (chat))
2086 DEBUG ("Can't invite contact to %s",
2087 tp_proxy_get_object_path (chat));
2091 if (self->priv->individual_mgr == NULL)
2092 /* Not likely as we have to focus out the chat window in order to start
2093 * the DnD but best to be safe. */
2096 individual = empathy_individual_manager_lookup_member (
2097 self->priv->individual_mgr, id);
2098 if (individual == NULL)
2100 DEBUG ("Failed to find individual %s", id);
2104 conn = tp_channel_get_connection ((TpChannel *) chat);
2105 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2106 if (tp_contact == NULL)
2108 DEBUG ("Can't find a TpContact on connection %s for %s",
2109 tp_proxy_get_object_path (conn), id);
2113 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2114 tp_channel_get_identifier ((TpChannel *) chat));
2116 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2117 empathy_tp_chat_add (chat, contact, NULL);
2118 g_object_unref (contact);
2121 gtk_drag_finish (context, TRUE, FALSE, time_);
2125 chat_window_drag_data_received (GtkWidget *widget,
2126 GdkDragContext *context,
2129 GtkSelectionData *selection,
2132 EmpathyChatWindow *self)
2134 if (info == DND_DRAG_TYPE_CONTACT_ID)
2136 EmpathyChat *chat = NULL;
2137 EmpathyChatWindow *old_window;
2138 TpAccount *account = NULL;
2139 EmpathyClientFactory *factory;
2142 const gchar *account_id;
2143 const gchar *contact_id;
2145 id = (const gchar*) gtk_selection_data_get_data (selection);
2147 factory = empathy_client_factory_dup ();
2149 DEBUG ("DND contact from roster with id:'%s'", id);
2151 strv = g_strsplit (id, ":", 2);
2152 if (g_strv_length (strv) == 2)
2154 account_id = strv[0];
2155 contact_id = strv[1];
2157 account = tp_simple_client_factory_ensure_account (
2158 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2160 g_object_unref (factory);
2161 if (account != NULL)
2162 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2165 if (account == NULL)
2168 gtk_drag_finish (context, FALSE, FALSE, time_);
2174 empathy_chat_with_contact_id (account, contact_id,
2175 empathy_get_current_action_time (), NULL, NULL);
2183 old_window = chat_window_find_chat (chat);
2186 if (old_window == self)
2188 gtk_drag_finish (context, TRUE, FALSE, time_);
2192 empathy_chat_window_move_chat (old_window, self, chat);
2196 empathy_chat_window_add_chat (self, chat);
2199 /* Added to take care of any outstanding chat events */
2200 empathy_chat_window_present_chat (chat,
2201 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2203 /* We should return TRUE to remove the data when doing
2204 * GDK_ACTION_MOVE, but we don't here otherwise it has
2205 * weird consequences, and we handle that internally
2206 * anyway with add_chat () and remove_chat ().
2208 gtk_drag_finish (context, TRUE, FALSE, time_);
2210 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2212 drag_data_received_individual_id (self, widget, context, x, y,
2213 selection, info, time_);
2215 else if (info == DND_DRAG_TYPE_URI_LIST)
2217 EmpathyContact *contact;
2220 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2222 /* contact is NULL when current_chat is a multi-user chat.
2223 * We don't do file transfers to MUCs, so just cancel the drag.
2225 if (contact == NULL)
2227 gtk_drag_finish (context, TRUE, FALSE, time_);
2231 data = (const gchar *) gtk_selection_data_get_data (selection);
2232 empathy_send_file_from_uri_list (contact, data);
2234 gtk_drag_finish (context, TRUE, FALSE, time_);
2236 else if (info == DND_DRAG_TYPE_TAB)
2239 EmpathyChatWindow *old_window = NULL;
2243 chat = (void *) gtk_selection_data_get_data (selection);
2244 old_window = chat_window_find_chat (*chat);
2248 self->priv->dnd_same_window = (old_window == self);
2250 DEBUG ("DND tab (within same window: %s)",
2251 self->priv->dnd_same_window ? "Yes" : "No");
2256 DEBUG ("DND from unknown source");
2257 gtk_drag_finish (context, FALSE, FALSE, time_);
2262 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2263 guint num_chats_in_manager,
2264 EmpathyChatWindow *self)
2266 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2267 num_chats_in_manager > 0);
2271 chat_window_finalize (GObject *object)
2273 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2275 DEBUG ("Finalized: %p", object);
2277 g_object_unref (self->priv->ui_manager);
2278 g_object_unref (self->priv->chatroom_manager);
2279 g_object_unref (self->priv->notify_mgr);
2280 g_object_unref (self->priv->gsettings_chat);
2281 g_object_unref (self->priv->gsettings_notif);
2282 g_object_unref (self->priv->gsettings_ui);
2283 g_object_unref (self->priv->sound_mgr);
2284 g_clear_object (&self->priv->individual_mgr);
2286 if (self->priv->notification != NULL)
2288 notify_notification_close (self->priv->notification, NULL);
2289 self->priv->notification = NULL;
2292 if (self->priv->contact_targets)
2293 gtk_target_list_unref (self->priv->contact_targets);
2295 if (self->priv->file_targets)
2296 gtk_target_list_unref (self->priv->file_targets);
2298 if (self->priv->chat_manager)
2300 g_signal_handler_disconnect (self->priv->chat_manager,
2301 self->priv->chat_manager_chats_changed_id);
2302 g_object_unref (self->priv->chat_manager);
2303 self->priv->chat_manager = NULL;
2306 chat_windows = g_list_remove (chat_windows, self);
2308 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2312 chat_window_get_property (GObject *object,
2317 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2319 switch (property_id)
2321 case PROP_INDIVIDUAL_MGR:
2322 g_value_set_object (value, self->priv->individual_mgr);
2324 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2330 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2332 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2335 object_class->get_property = chat_window_get_property;
2336 object_class->finalize = chat_window_finalize;
2338 spec = g_param_spec_object ("individual-manager", "individual-manager",
2339 "EmpathyIndividualManager",
2340 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2341 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2342 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2344 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2348 empathy_chat_window_init (EmpathyChatWindow *self)
2351 GtkAccelGroup *accel_group;
2356 GtkWidget *chat_vbox;
2358 EmpathySmileyManager *smiley_manager;
2360 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2361 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2363 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2364 gui = empathy_builder_get_file (filename,
2365 "chat_vbox", &chat_vbox,
2366 "ui_manager", &self->priv->ui_manager,
2367 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2368 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2369 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2370 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2371 "menu_edit_cut", &self->priv->menu_edit_cut,
2372 "menu_edit_copy", &self->priv->menu_edit_copy,
2373 "menu_edit_paste", &self->priv->menu_edit_paste,
2374 "menu_edit_find", &self->priv->menu_edit_find,
2375 "menu_tabs_next", &self->priv->menu_tabs_next,
2376 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2377 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2378 "menu_tabs_left", &self->priv->menu_tabs_left,
2379 "menu_tabs_right", &self->priv->menu_tabs_right,
2380 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2384 empathy_builder_connect (gui, self,
2385 "menu_conv", "activate", chat_window_conv_activate_cb,
2386 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2387 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2388 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2389 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2390 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2391 "menu_conv_close", "activate", chat_window_close_activate_cb,
2392 "menu_edit", "activate", chat_window_edit_activate_cb,
2393 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2394 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2395 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2396 "menu_edit_find", "activate", chat_window_find_activate_cb,
2397 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2398 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2399 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2400 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2401 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2402 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2403 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2404 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2407 empathy_set_css_provider (GTK_WIDGET (self));
2409 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2410 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2411 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2412 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2414 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2416 self->priv->notebook = gtk_notebook_new ();
2418 g_signal_connect (self->priv->notebook, "create-window",
2419 G_CALLBACK (notebook_create_window_cb), self);
2421 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2423 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2424 "EmpathyChatWindow");
2425 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2426 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2427 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2428 gtk_widget_show (self->priv->notebook);
2431 accel_group = gtk_accel_group_new ();
2432 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2434 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2436 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2439 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2443 g_object_unref (accel_group);
2445 /* Set up drag target lists */
2446 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2447 G_N_ELEMENTS (drag_types_dest_contact));
2449 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2450 G_N_ELEMENTS (drag_types_dest_file));
2452 /* Set up smiley menu */
2453 smiley_manager = empathy_smiley_manager_dup_singleton ();
2454 submenu = empathy_smiley_menu_new (smiley_manager,
2455 chat_window_insert_smiley_activate_cb, self);
2457 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2458 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2459 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2460 g_object_unref (smiley_manager);
2462 /* Set up signals we can't do with ui file since we may need to
2463 * block/unblock them at some later stage.
2466 g_signal_connect (self, "delete_event",
2467 G_CALLBACK (chat_window_delete_event_cb), self);
2468 g_signal_connect (self, "focus_in_event",
2469 G_CALLBACK (chat_window_focus_in_event_cb), self);
2470 g_signal_connect (self, "focus_out_event",
2471 G_CALLBACK (chat_window_focus_out_event_cb), self);
2472 g_signal_connect_after (self->priv->notebook, "switch_page",
2473 G_CALLBACK (chat_window_page_switched_cb), self);
2474 g_signal_connect (self->priv->notebook, "page_added",
2475 G_CALLBACK (chat_window_page_added_cb), self);
2476 g_signal_connect (self->priv->notebook, "page_removed",
2477 G_CALLBACK (chat_window_page_removed_cb), self);
2479 /* Set up drag and drop */
2480 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2481 GTK_DEST_DEFAULT_HIGHLIGHT,
2483 G_N_ELEMENTS (drag_types_dest),
2484 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2486 /* connect_after to allow GtkNotebook's built-in tab switching */
2487 g_signal_connect_after (self->priv->notebook, "drag-motion",
2488 G_CALLBACK (chat_window_drag_motion), self);
2489 g_signal_connect (self->priv->notebook, "drag-data-received",
2490 G_CALLBACK (chat_window_drag_data_received), self);
2491 g_signal_connect (self->priv->notebook, "drag-drop",
2492 G_CALLBACK (chat_window_drag_drop), self);
2494 chat_windows = g_list_prepend (chat_windows, self);
2496 /* Set up private details */
2497 self->priv->chats = NULL;
2498 self->priv->current_chat = NULL;
2499 self->priv->notification = NULL;
2501 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2503 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2504 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2505 self->priv->chat_manager, "closed-chats-changed",
2506 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2508 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2509 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2511 g_object_ref (self->priv->ui_manager);
2512 g_object_unref (gui);
2515 /* Returns the window to open a new tab in if there is a suitable window,
2516 * otherwise, returns NULL indicating that a new window should be added.
2518 static EmpathyChatWindow *
2519 empathy_chat_window_get_default (gboolean room)
2521 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2523 gboolean separate_windows = TRUE;
2525 separate_windows = g_settings_get_boolean (gsettings,
2526 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2528 g_object_unref (gsettings);
2530 if (separate_windows)
2531 /* Always create a new window */
2534 for (l = chat_windows; l; l = l->next)
2536 EmpathyChatWindow *chat_window;
2537 guint nb_rooms, nb_private;
2539 chat_window = l->data;
2541 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2543 /* Skip the window if there aren't any rooms in it */
2544 if (room && nb_rooms == 0)
2547 /* Skip the window if there aren't any 1-1 chats in it */
2548 if (!room && nb_private == 0)
2558 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2562 GtkWidget *popup_label;
2564 GValue value = { 0, };
2566 g_return_if_fail (self != NULL);
2567 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2569 /* Reference the chat object */
2570 g_object_ref (chat);
2572 /* If this window has just been created, position it */
2573 if (self->priv->chats == NULL)
2575 const gchar *name = "chat-window";
2576 gboolean separate_windows;
2578 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2579 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2581 if (empathy_chat_is_room (chat))
2582 name = "room-window";
2584 if (separate_windows)
2588 /* Save current position of the window */
2589 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2591 /* First bind to the 'generic' name. So new window for which we didn't
2592 * save a geometry yet will have the geometry of the last saved
2593 * window (bgo #601191). */
2594 empathy_geometry_bind (GTK_WINDOW (self), name);
2596 /* Restore previous position of the window so the newly created window
2597 * won't be in the same position as the latest saved window and so
2598 * completely hide it. */
2599 gtk_window_move (GTK_WINDOW (self), x, y);
2601 /* Then bind it to the name of the contact/room so we'll save the
2602 * geometry specific to this window */
2603 name = empathy_chat_get_id (chat);
2606 empathy_geometry_bind (GTK_WINDOW (self), name);
2609 child = GTK_WIDGET (chat);
2610 label = chat_window_create_label (self, chat, TRUE);
2611 popup_label = chat_window_create_label (self, chat, FALSE);
2612 gtk_widget_show (child);
2614 g_signal_connect (chat, "notify::name",
2615 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2616 g_signal_connect (chat, "notify::subject",
2617 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2618 g_signal_connect (chat, "notify::remote-contact",
2619 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2620 g_signal_connect (chat, "notify::sms-channel",
2621 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2622 g_signal_connect (chat, "notify::n-messages-sending",
2623 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2624 g_signal_connect (chat, "notify::nb-unread-messages",
2625 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2626 chat_window_chat_notify_cb (chat);
2628 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2630 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2631 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2632 g_value_init (&value, G_TYPE_BOOLEAN);
2633 g_value_set_boolean (&value, TRUE);
2634 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2635 child, "tab-expand" , &value);
2636 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2637 child, "tab-fill" , &value);
2638 g_value_unset (&value);
2640 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2644 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2648 EmpathyContact *remote_contact;
2649 EmpathyChatManager *chat_manager;
2651 g_return_if_fail (self != NULL);
2652 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2654 g_signal_handlers_disconnect_by_func (chat,
2655 chat_window_chat_notify_cb, NULL);
2657 remote_contact = g_object_get_data (G_OBJECT (chat),
2658 "chat-window-remote-contact");
2662 g_signal_handlers_disconnect_by_func (remote_contact,
2663 chat_window_update_chat_tab, chat);
2666 chat_manager = empathy_chat_manager_dup_singleton ();
2667 empathy_chat_manager_closed_chat (chat_manager, chat);
2668 g_object_unref (chat_manager);
2670 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2672 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2674 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2676 g_object_unref (chat);
2680 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2681 EmpathyChatWindow *new_window,
2686 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2687 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2688 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2690 widget = GTK_WIDGET (chat);
2692 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2693 G_OBJECT (widget)->ref_count);
2695 /* We reference here to make sure we don't loose the widget
2696 * and the EmpathyChat object during the move.
2698 g_object_ref (chat);
2699 g_object_ref (widget);
2701 empathy_chat_window_remove_chat (old_window, chat);
2702 empathy_chat_window_add_chat (new_window, chat);
2704 g_object_unref (widget);
2705 g_object_unref (chat);
2709 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2714 g_return_if_fail (self != NULL);
2715 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2717 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2720 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2725 empathy_chat_window_find_chat (TpAccount *account,
2727 gboolean sms_channel)
2731 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2733 for (l = chat_windows; l; l = l->next)
2735 EmpathyChatWindow *window = l->data;
2738 for (ll = window->priv->chats; ll; ll = ll->next)
2744 if (account == empathy_chat_get_account (chat) &&
2745 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2746 sms_channel == empathy_chat_is_sms_channel (chat))
2755 empathy_chat_window_present_chat (EmpathyChat *chat,
2758 EmpathyChatWindow *self;
2759 guint32 x_timestamp;
2761 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2763 self = chat_window_find_chat (chat);
2765 /* If the chat has no window, create one */
2768 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2771 self = empathy_chat_window_new ();
2773 /* we want to display the newly created window even if we
2774 * don't present it */
2775 gtk_widget_show (GTK_WIDGET (self));
2778 empathy_chat_window_add_chat (self, chat);
2781 /* Don't force the window to show itself when it wasn't
2782 * an action by the user
2784 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2787 if (x_timestamp != GDK_CURRENT_TIME)
2789 /* Don't present or switch tab if the action was earlier than the
2790 * last actions X time, accounting for overflow and the first ever
2793 if (self->priv->x_user_action_time != 0
2794 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2797 self->priv->x_user_action_time = x_timestamp;
2800 empathy_chat_window_switch_to_chat (self, chat);
2802 /* Don't use empathy_window_present_with_time () which would move the window
2803 * to our current desktop but move to the window's desktop instead. This is
2804 * more coherent with Shell's 'app is ready' notication which moves the view
2805 * to the app desktop rather than moving the app itself. */
2806 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2808 gtk_widget_grab_focus (chat->input_text_view);
2813 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2818 guint _nb_rooms = 0, _nb_private = 0;
2820 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2822 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2828 if (nb_rooms != NULL)
2829 *nb_rooms = _nb_rooms;
2830 if (nb_private != NULL)
2831 *nb_private = _nb_private;
2834 EmpathyIndividualManager *
2835 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2837 return self->priv->individual_mgr;