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;
86 NotifyNotification *notification;
88 GtkTargetList *contact_targets;
89 GtkTargetList *file_targets;
91 EmpathyChatManager *chat_manager;
92 gulong chat_manager_chats_changed_id;
95 GtkUIManager *ui_manager;
96 GtkAction *menu_conv_insert_smiley;
97 GtkAction *menu_conv_favorite;
98 GtkAction *menu_conv_always_urgent;
99 GtkAction *menu_conv_toggle_contacts;
101 GtkAction *menu_edit_cut;
102 GtkAction *menu_edit_copy;
103 GtkAction *menu_edit_paste;
104 GtkAction *menu_edit_find;
106 GtkAction *menu_tabs_next;
107 GtkAction *menu_tabs_prev;
108 GtkAction *menu_tabs_undo_close_tab;
109 GtkAction *menu_tabs_left;
110 GtkAction *menu_tabs_right;
111 GtkAction *menu_tabs_detach;
113 /* Last user action time we acted upon to show a tab */
114 guint32 x_user_action_time;
116 GSettings *gsettings_chat;
117 GSettings *gsettings_notif;
118 GSettings *gsettings_ui;
120 EmpathySoundManager *sound_mgr;
122 gboolean updating_menu;
125 static GList *chat_windows = NULL;
127 static const guint tab_accel_keys[] =
129 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
130 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
135 DND_DRAG_TYPE_CONTACT_ID,
136 DND_DRAG_TYPE_INDIVIDUAL_ID,
137 DND_DRAG_TYPE_URI_LIST,
141 static const GtkTargetEntry drag_types_dest[] =
143 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
144 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
145 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
146 /* FIXME: disabled because of bug #640513
147 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
148 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
152 static const GtkTargetEntry drag_types_dest_contact[] =
154 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
155 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
158 static const GtkTargetEntry drag_types_dest_file[] =
160 /* must be first to be prioritized, in order to receive the
161 * note's file path from Tomboy instead of an URI */
162 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
163 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
166 static void chat_window_update (EmpathyChatWindow *window,
167 gboolean update_contact_menu);
169 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
172 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
175 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
176 EmpathyChatWindow *new_window,
179 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
183 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT)
186 chat_window_accel_cb (GtkAccelGroup *accelgroup,
190 EmpathyChatWindow *self)
195 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
197 if (tab_accel_keys[i] == key)
205 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
208 static EmpathyChatWindow *
209 chat_window_find_chat (EmpathyChat *chat)
213 for (l = chat_windows; l; l = l->next)
215 EmpathyChatWindow *window = l->data;
217 ll = g_list_find (window->priv->chats, chat);
226 remove_all_chats (EmpathyChatWindow *self)
230 while (self->priv->chats)
231 empathy_chat_window_remove_chat (self, self->priv->chats->data);
233 g_object_unref (self);
237 confirm_close_response_cb (GtkWidget *dialog,
239 EmpathyChatWindow *window)
243 chat = g_object_get_data (G_OBJECT (dialog), "chat");
245 gtk_widget_destroy (dialog);
247 if (response != GTK_RESPONSE_ACCEPT)
251 empathy_chat_window_remove_chat (window, chat);
253 remove_all_chats (window);
257 confirm_close (EmpathyChatWindow *self,
258 gboolean close_window,
263 gchar *primary, *secondary;
265 g_return_if_fail (n_rooms > 0);
268 g_return_if_fail (chat == NULL);
270 g_return_if_fail (chat != NULL);
272 /* If there are no chats in this window, how could we possibly have got
275 g_return_if_fail (self->priv->chats != NULL);
277 /* Treat closing a window which only has one tab exactly like closing
280 if (close_window && self->priv->chats->next == NULL)
282 close_window = FALSE;
283 chat = self->priv->chats->data;
288 primary = g_strdup (_("Close this window?"));
292 gchar *chat_name = empathy_chat_dup_name (chat);
293 secondary = g_strdup_printf (
294 _("Closing this window will leave %s. You will "
295 "not receive any further messages until you "
302 secondary = g_strdup_printf (
303 /* Note to translators: the number of chats will
304 * always be at least 2.
307 "Closing this window will leave a chat room. You will "
308 "not receive any further messages until you rejoin it.",
309 "Closing this window will leave %u chat rooms. You will "
310 "not receive any further messages until you rejoin them.",
317 gchar *chat_name = empathy_chat_dup_name (chat);
318 primary = g_strdup_printf (_("Leave %s?"), chat_name);
319 secondary = g_strdup (
320 _("You will not receive any further messages from this chat "
321 "room until you rejoin it."));
325 dialog = gtk_message_dialog_new (
326 GTK_WINDOW (self->priv->dialog),
327 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
332 gtk_window_set_title (GTK_WINDOW (dialog), "");
333 g_object_set (dialog, "secondary-text", secondary, NULL);
338 gtk_dialog_add_button (GTK_DIALOG (dialog),
339 close_window ? _("Close window") : _("Leave room"),
340 GTK_RESPONSE_ACCEPT);
341 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
342 GTK_RESPONSE_ACCEPT);
345 g_object_set_data (G_OBJECT (dialog), "chat", chat);
347 g_signal_connect (dialog, "response",
348 G_CALLBACK (confirm_close_response_cb), self);
350 gtk_window_present (GTK_WINDOW (dialog));
353 /* Returns TRUE if we should check if the user really wants to leave. If it's
354 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
355 * the user is actually in the room as opposed to having been kicked or gone
356 * offline or something), then we should check.
359 chat_needs_close_confirmation (EmpathyChat *chat)
361 return (empathy_chat_is_room (chat) &&
362 empathy_chat_get_tp_chat (chat) != NULL);
366 maybe_close_chat (EmpathyChatWindow *window,
369 g_return_if_fail (chat != NULL);
371 if (chat_needs_close_confirmation (chat))
372 confirm_close (window, FALSE, 1, chat);
374 empathy_chat_window_remove_chat (window, chat);
378 chat_window_close_clicked_cb (GtkAction *action,
381 EmpathyChatWindow *window;
383 window = chat_window_find_chat (chat);
384 maybe_close_chat (window, chat);
388 chat_tab_style_updated_cb (GtkWidget *hbox,
392 int char_width, h, w;
393 PangoContext *context;
394 const PangoFontDescription *font_desc;
395 PangoFontMetrics *metrics;
397 button = g_object_get_data (G_OBJECT (user_data),
398 "chat-window-tab-close-button");
399 context = gtk_widget_get_pango_context (hbox);
401 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
402 GTK_STATE_FLAG_NORMAL);
404 metrics = pango_context_get_metrics (context, font_desc,
405 pango_context_get_language (context));
406 char_width = pango_font_metrics_get_approximate_char_width (metrics);
407 pango_font_metrics_unref (metrics);
409 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
410 GTK_ICON_SIZE_MENU, &w, &h);
412 /* Request at least about 12 chars width plus at least space for the status
413 * image and the close button */
414 gtk_widget_set_size_request (hbox,
415 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
417 gtk_widget_set_size_request (button, w, h);
421 create_close_button (void)
423 GtkWidget *button, *image;
424 GtkStyleContext *context;
426 button = gtk_button_new ();
428 context = gtk_widget_get_style_context (button);
429 gtk_style_context_add_class (context, "empathy-tab-close-button");
431 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
432 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
434 /* We don't want focus/keynav for the button to avoid clutter, and
435 * Ctrl-W works anyway.
437 gtk_widget_set_can_focus (button, FALSE);
438 gtk_widget_set_can_default (button, FALSE);
440 image = gtk_image_new_from_icon_name ("window-close-symbolic",
442 gtk_widget_show (image);
444 gtk_container_add (GTK_CONTAINER (button), image);
450 chat_window_create_label (EmpathyChatWindow *window,
452 gboolean is_tab_label)
455 GtkWidget *name_label;
456 GtkWidget *status_image;
457 GtkWidget *event_box;
458 GtkWidget *event_box_hbox;
459 PangoAttrList *attr_list;
460 PangoAttribute *attr;
462 /* The spacing between the button and the label. */
463 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
465 event_box = gtk_event_box_new ();
466 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
468 name_label = gtk_label_new (NULL);
470 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
472 attr_list = pango_attr_list_new ();
473 attr = pango_attr_scale_new (1/1.2);
474 attr->start_index = 0;
475 attr->end_index = -1;
476 pango_attr_list_insert (attr_list, attr);
477 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
478 pango_attr_list_unref (attr_list);
480 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
481 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
482 g_object_set_data (G_OBJECT (chat),
483 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
486 status_image = gtk_image_new ();
488 /* Spacing between the icon and label. */
489 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
491 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
492 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
494 g_object_set_data (G_OBJECT (chat),
495 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
497 g_object_set_data (G_OBJECT (chat),
498 is_tab_label ? "chat-window-tab-tooltip-widget" :
499 "chat-window-menu-tooltip-widget",
502 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
503 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
507 GtkWidget *close_button;
508 GtkWidget *sending_spinner;
510 sending_spinner = gtk_spinner_new ();
512 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
514 g_object_set_data (G_OBJECT (chat),
515 "chat-window-tab-sending-spinner",
518 close_button = create_close_button ();
519 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
522 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
524 g_signal_connect (close_button,
526 G_CALLBACK (chat_window_close_clicked_cb), chat);
528 /* React to theme changes and also setup the size correctly. */
529 g_signal_connect (hbox, "style-updated",
530 G_CALLBACK (chat_tab_style_updated_cb), chat);
533 gtk_widget_show_all (hbox);
539 _submenu_notify_visible_changed_cb (GObject *object,
543 g_signal_handlers_disconnect_by_func (object,
544 _submenu_notify_visible_changed_cb, userdata);
546 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
550 chat_window_menu_context_update (EmpathyChatWindow *self,
555 gboolean wrap_around;
556 gboolean is_connected;
559 page_num = gtk_notebook_get_current_page (
560 GTK_NOTEBOOK (self->priv->notebook));
561 first_page = (page_num == 0);
562 last_page = (page_num == (num_pages - 1));
563 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
565 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
567 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
569 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
571 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
572 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
573 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
574 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
578 chat_window_conversation_menu_update (EmpathyChatWindow *self)
580 EmpathyTpChat *tp_chat;
581 TpConnection *connection;
583 gboolean sensitive = FALSE;
585 g_return_if_fail (self->priv->current_chat != NULL);
587 action = gtk_ui_manager_get_action (self->priv->ui_manager,
588 "/chats_menubar/menu_conv/menu_conv_invite_participant");
589 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
593 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
595 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
596 (tp_connection_get_status (connection, NULL) ==
597 TP_CONNECTION_STATUS_CONNECTED);
600 gtk_action_set_sensitive (action, sensitive);
604 chat_window_contact_menu_update (EmpathyChatWindow *self)
606 GtkWidget *menu, *submenu, *orig_submenu;
608 if (self->priv->updating_menu)
610 self->priv->updating_menu = TRUE;
612 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
613 "/chats_menubar/menu_contact");
614 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
616 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
618 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
622 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
623 g_object_set_data (G_OBJECT (submenu), "window", self->priv->dialog);
625 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
626 gtk_widget_show (menu);
627 gtk_widget_set_sensitive (menu, TRUE);
631 gtk_widget_set_sensitive (menu, FALSE);
636 tp_g_signal_connect_object (orig_submenu,
638 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
641 self->priv->updating_menu = FALSE;
645 get_all_unread_messages (EmpathyChatWindow *self)
650 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
651 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
657 get_window_title_name (EmpathyChatWindow *self)
659 gchar *active_name, *ret;
661 guint current_unread_msgs;
663 nb_chats = g_list_length (self->priv->chats);
664 g_assert (nb_chats > 0);
666 active_name = empathy_chat_dup_name (self->priv->current_chat);
668 current_unread_msgs = empathy_chat_get_nb_unread_messages (
669 self->priv->current_chat);
674 if (current_unread_msgs == 0)
675 ret = g_strdup (active_name);
677 ret = g_strdup_printf (ngettext (
679 "%s (%d unread)", current_unread_msgs),
680 active_name, current_unread_msgs);
684 guint nb_others = nb_chats - 1;
685 guint all_unread_msgs;
687 all_unread_msgs = get_all_unread_messages (self);
689 if (all_unread_msgs == 0)
691 /* no unread message */
692 ret = g_strdup_printf (ngettext (
694 "%s (and %u others)", nb_others),
695 active_name, nb_others);
697 else if (all_unread_msgs == current_unread_msgs)
699 /* unread messages are in the current tab */
700 ret = g_strdup_printf (ngettext (
702 "%s (%d unread)", current_unread_msgs),
703 active_name, current_unread_msgs);
705 else if (current_unread_msgs == 0)
707 /* unread messages are in other tabs */
708 ret = g_strdup_printf (ngettext (
709 "%s (%d unread from others)",
710 "%s (%d unread from others)",
712 active_name, all_unread_msgs);
716 /* unread messages are in all the tabs */
717 ret = g_strdup_printf (ngettext (
718 "%s (%d unread from all)",
719 "%s (%d unread from all)",
721 active_name, all_unread_msgs);
725 g_free (active_name);
731 chat_window_title_update (EmpathyChatWindow *self)
735 name = get_window_title_name (self);
736 gtk_window_set_title (GTK_WINDOW (self->priv->dialog), name);
741 chat_window_icon_update (EmpathyChatWindow *self,
742 gboolean new_messages)
745 EmpathyContact *remote_contact;
746 gboolean avatar_in_icon;
749 n_chats = g_list_length (self->priv->chats);
751 /* Update window icon */
754 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog),
755 EMPATHY_IMAGE_MESSAGE);
759 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
760 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
762 if (n_chats == 1 && avatar_in_icon)
764 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
765 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
767 gtk_window_set_icon (GTK_WINDOW (self->priv->dialog), icon);
770 g_object_unref (icon);
774 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog), NULL);
780 chat_window_close_button_update (EmpathyChatWindow *self,
784 GtkWidget *chat_close_button;
789 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
790 chat_close_button = g_object_get_data (G_OBJECT (chat),
791 "chat-window-tab-close-button");
792 gtk_widget_hide (chat_close_button);
796 for (i=0; i<num_pages; i++)
798 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
799 chat_close_button = g_object_get_data (G_OBJECT (chat),
800 "chat-window-tab-close-button");
801 gtk_widget_show (chat_close_button);
807 chat_window_update (EmpathyChatWindow *self,
808 gboolean update_contact_menu)
812 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
814 /* Update Tab menu */
815 chat_window_menu_context_update (self, num_pages);
817 chat_window_conversation_menu_update (self);
819 /* If this update is due to a focus-in event, we know the menu will be
820 the same as when we last left it, so no work to do. Besides, if we
821 swap out the menu on a focus-in, we may confuse any external global
823 if (update_contact_menu)
825 chat_window_contact_menu_update (self);
828 chat_window_title_update (self);
830 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
832 chat_window_close_button_update (self, num_pages);
836 append_markup_printf (GString *string,
843 va_start (args, format);
845 tmp = g_markup_vprintf_escaped (format, args);
846 g_string_append (string, tmp);
853 chat_window_update_chat_tab_full (EmpathyChat *chat,
854 gboolean update_contact_menu)
856 EmpathyChatWindow *self;
857 EmpathyContact *remote_contact;
861 const gchar *subject;
862 const gchar *status = NULL;
866 const gchar *icon_name;
867 GtkWidget *tab_image;
868 GtkWidget *menu_image;
869 GtkWidget *sending_spinner;
872 self = chat_window_find_chat (chat);
876 /* Get information */
877 name = empathy_chat_dup_name (chat);
878 account = empathy_chat_get_account (chat);
879 subject = empathy_chat_get_subject (chat);
880 remote_contact = empathy_chat_get_remote_contact (chat);
882 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
884 name, tp_proxy_get_object_path (account), subject, remote_contact);
886 /* Update tab image */
887 if (empathy_chat_get_tp_chat (chat) == NULL)
889 /* No TpChat, we are disconnected */
892 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
894 icon_name = EMPATHY_IMAGE_MESSAGE;
896 else if (remote_contact && empathy_chat_is_composing (chat))
898 icon_name = EMPATHY_IMAGE_TYPING;
900 else if (empathy_chat_is_sms_channel (chat))
902 icon_name = EMPATHY_IMAGE_SMS;
904 else if (remote_contact)
906 icon_name = empathy_icon_name_for_contact (remote_contact);
910 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
913 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
914 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
916 if (icon_name != NULL)
918 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
920 gtk_widget_show (tab_image);
921 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
923 gtk_widget_show (menu_image);
927 gtk_widget_hide (tab_image);
928 gtk_widget_hide (menu_image);
931 /* Update the sending spinner */
932 nb_sending = empathy_chat_get_n_messages_sending (chat);
933 sending_spinner = g_object_get_data (G_OBJECT (chat),
934 "chat-window-tab-sending-spinner");
936 g_object_set (sending_spinner,
937 "active", nb_sending > 0,
938 "visible", nb_sending > 0,
941 /* Update tab tooltip */
942 tooltip = g_string_new (NULL);
946 id = empathy_contact_get_id (remote_contact);
947 status = empathy_contact_get_presence_message (remote_contact);
954 if (empathy_chat_is_sms_channel (chat))
955 append_markup_printf (tooltip, "%s ", _("SMS:"));
957 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
958 id, tp_account_get_display_name (account));
962 char *tmp = g_strdup_printf (
963 ngettext ("Sending %d message",
964 "Sending %d messages",
968 g_string_append (tooltip, "\n");
969 g_string_append (tooltip, tmp);
971 gtk_widget_set_tooltip_text (sending_spinner, tmp);
975 if (!EMP_STR_EMPTY (status))
976 append_markup_printf (tooltip, "\n<i>%s</i>", status);
978 if (!EMP_STR_EMPTY (subject))
979 append_markup_printf (tooltip, "\n<b>%s</b> %s",
980 _("Topic:"), subject);
982 if (remote_contact && empathy_chat_is_composing (chat))
983 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
985 if (remote_contact != NULL)
987 const gchar * const *types;
989 types = empathy_contact_get_client_types (remote_contact);
990 if (types != NULL && !tp_strdiff (types[0], "phone"))
992 /* I'm on a phone ! */
995 name = g_strdup_printf ("☎ %s", name);
1000 markup = g_string_free (tooltip, FALSE);
1001 widget = g_object_get_data (G_OBJECT (chat),
1002 "chat-window-tab-tooltip-widget");
1003 gtk_widget_set_tooltip_markup (widget, markup);
1005 widget = g_object_get_data (G_OBJECT (chat),
1006 "chat-window-menu-tooltip-widget");
1007 gtk_widget_set_tooltip_markup (widget, markup);
1010 /* Update tab and menu label */
1011 if (empathy_chat_is_highlighted (chat))
1013 markup = g_markup_printf_escaped (
1014 "<span color=\"red\" weight=\"bold\">%s</span>",
1019 markup = g_markup_escape_text (name, -1);
1022 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1023 gtk_label_set_markup (GTK_LABEL (widget), markup);
1024 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1025 gtk_label_set_markup (GTK_LABEL (widget), markup);
1028 /* Update the window if it's the current chat */
1029 if (self->priv->current_chat == chat)
1030 chat_window_update (self, update_contact_menu);
1036 chat_window_update_chat_tab (EmpathyChat *chat)
1038 chat_window_update_chat_tab_full (chat, TRUE);
1042 chat_window_chat_notify_cb (EmpathyChat *chat)
1044 EmpathyChatWindow *window;
1045 EmpathyContact *old_remote_contact;
1046 EmpathyContact *remote_contact = NULL;
1048 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1049 "chat-window-remote-contact");
1050 remote_contact = empathy_chat_get_remote_contact (chat);
1052 if (old_remote_contact != remote_contact)
1054 /* The remote-contact associated with the chat changed, we need
1055 * to keep track of any change of that contact and update the
1056 * window each time. */
1058 g_signal_connect_swapped (remote_contact, "notify",
1059 G_CALLBACK (chat_window_update_chat_tab), chat);
1061 if (old_remote_contact)
1062 g_signal_handlers_disconnect_by_func (old_remote_contact,
1063 chat_window_update_chat_tab, chat);
1065 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1066 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1069 chat_window_update_chat_tab (chat);
1071 window = chat_window_find_chat (chat);
1073 chat_window_update (window, FALSE);
1077 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1078 EmpathySmiley *smiley,
1081 EmpathyChatWindow *self = user_data;
1083 GtkTextBuffer *buffer;
1086 chat = self->priv->current_chat;
1088 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1089 gtk_text_buffer_get_end_iter (buffer, &iter);
1090 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1094 chat_window_conv_activate_cb (GtkAction *action,
1095 EmpathyChatWindow *self)
1099 EmpathyContact *remote_contact = NULL;
1101 /* Favorite room menu */
1102 is_room = empathy_chat_is_room (self->priv->current_chat);
1107 gboolean found = FALSE;
1108 EmpathyChatroom *chatroom;
1110 room = empathy_chat_get_id (self->priv->current_chat);
1111 account = empathy_chat_get_account (self->priv->current_chat);
1112 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1115 if (chatroom != NULL)
1116 found = empathy_chatroom_is_favorite (chatroom);
1118 DEBUG ("This room %s favorite", found ? "is" : "is not");
1119 gtk_toggle_action_set_active (
1120 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1122 if (chatroom != NULL)
1123 found = empathy_chatroom_is_always_urgent (chatroom);
1125 gtk_toggle_action_set_active (
1126 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1129 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1130 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1132 /* Show contacts menu */
1133 g_object_get (self->priv->current_chat,
1134 "remote-contact", &remote_contact,
1135 "show-contacts", &active,
1138 if (remote_contact == NULL)
1140 gtk_toggle_action_set_active (
1141 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1144 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1145 (remote_contact == NULL));
1147 if (remote_contact != NULL)
1148 g_object_unref (remote_contact);
1152 chat_window_clear_activate_cb (GtkAction *action,
1153 EmpathyChatWindow *self)
1155 empathy_chat_clear (self->priv->current_chat);
1159 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1160 EmpathyChatWindow *self)
1166 EmpathyChatroom *chatroom;
1168 active = gtk_toggle_action_get_active (toggle_action);
1169 account = empathy_chat_get_account (self->priv->current_chat);
1170 room = empathy_chat_get_id (self->priv->current_chat);
1171 name = empathy_chat_dup_name (self->priv->current_chat);
1173 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1174 account, room, name);
1176 empathy_chatroom_set_favorite (chatroom, active);
1177 g_object_unref (chatroom);
1182 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1183 EmpathyChatWindow *self)
1189 EmpathyChatroom *chatroom;
1191 active = gtk_toggle_action_get_active (toggle_action);
1192 account = empathy_chat_get_account (self->priv->current_chat);
1193 room = empathy_chat_get_id (self->priv->current_chat);
1194 name = empathy_chat_dup_name (self->priv->current_chat);
1196 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1197 account, room, name);
1199 empathy_chatroom_set_always_urgent (chatroom, active);
1200 g_object_unref (chatroom);
1205 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1206 EmpathyChatWindow *self)
1210 active = gtk_toggle_action_get_active (toggle_action);
1212 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1216 chat_window_invite_participant_activate_cb (GtkAction *action,
1217 EmpathyChatWindow *self)
1220 EmpathyTpChat *tp_chat;
1223 g_return_if_fail (self->priv->current_chat != NULL);
1225 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1227 dialog = empathy_invite_participant_dialog_new (
1228 GTK_WINDOW (self->priv->dialog), tp_chat);
1230 gtk_widget_show (dialog);
1232 response = gtk_dialog_run (GTK_DIALOG (dialog));
1234 if (response == GTK_RESPONSE_ACCEPT)
1236 TpContact *tp_contact;
1237 EmpathyContact *contact;
1239 tp_contact = empathy_invite_participant_dialog_get_selected (
1240 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1241 if (tp_contact == NULL)
1244 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1246 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1248 g_object_unref (contact);
1252 gtk_widget_destroy (dialog);
1256 chat_window_close_activate_cb (GtkAction *action,
1257 EmpathyChatWindow *self)
1259 g_return_if_fail (self->priv->current_chat != NULL);
1261 maybe_close_chat (self, self->priv->current_chat);
1265 chat_window_edit_activate_cb (GtkAction *action,
1266 EmpathyChatWindow *self)
1268 GtkClipboard *clipboard;
1269 GtkTextBuffer *buffer;
1270 gboolean text_available;
1272 g_return_if_fail (self->priv->current_chat != NULL);
1274 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1276 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1277 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1278 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1282 buffer = gtk_text_view_get_buffer (
1283 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1285 if (gtk_text_buffer_get_has_selection (buffer))
1287 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1288 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1294 selection = empathy_theme_adium_get_has_selection (
1295 self->priv->current_chat->view);
1297 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1298 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1301 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1302 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1303 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1307 chat_window_cut_activate_cb (GtkAction *action,
1308 EmpathyChatWindow *self)
1310 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1312 empathy_chat_cut (self->priv->current_chat);
1316 chat_window_copy_activate_cb (GtkAction *action,
1317 EmpathyChatWindow *self)
1319 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1321 empathy_chat_copy (self->priv->current_chat);
1325 chat_window_paste_activate_cb (GtkAction *action,
1326 EmpathyChatWindow *self)
1328 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1330 empathy_chat_paste (self->priv->current_chat);
1334 chat_window_find_activate_cb (GtkAction *action,
1335 EmpathyChatWindow *self)
1337 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1339 empathy_chat_find (self->priv->current_chat);
1343 chat_window_tabs_next_activate_cb (GtkAction *action,
1344 EmpathyChatWindow *self)
1346 gint index_, numPages;
1347 gboolean wrap_around;
1349 g_object_get (gtk_settings_get_default (),
1350 "gtk-keynav-wrap-around", &wrap_around,
1353 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1354 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1356 if (index_ == (numPages - 1) && wrap_around)
1358 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1362 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1366 chat_window_tabs_previous_activate_cb (GtkAction *action,
1367 EmpathyChatWindow *self)
1369 gint index_, numPages;
1370 gboolean wrap_around;
1372 g_object_get (gtk_settings_get_default (),
1373 "gtk-keynav-wrap-around", &wrap_around,
1376 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1377 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1379 if (index_ <= 0 && wrap_around)
1381 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1386 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1390 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1391 EmpathyChatWindow *self)
1393 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1394 empathy_get_current_action_time ());
1398 chat_window_tabs_left_activate_cb (GtkAction *action,
1399 EmpathyChatWindow *self)
1402 gint index_, num_pages;
1404 chat = self->priv->current_chat;
1405 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1409 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1412 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1413 chat_window_menu_context_update (self, num_pages);
1417 chat_window_tabs_right_activate_cb (GtkAction *action,
1418 EmpathyChatWindow *self)
1421 gint index_, num_pages;
1423 chat = self->priv->current_chat;
1424 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1426 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1429 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1430 chat_window_menu_context_update (self, num_pages);
1433 static EmpathyChatWindow *
1434 empathy_chat_window_new (void)
1436 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1440 chat_window_detach_activate_cb (GtkAction *action,
1441 EmpathyChatWindow *self)
1443 EmpathyChatWindow *new_window;
1446 chat = self->priv->current_chat;
1447 new_window = empathy_chat_window_new ();
1449 empathy_chat_window_move_chat (self, new_window, chat);
1451 gtk_widget_show (new_window->priv->dialog);
1455 chat_window_help_contents_activate_cb (GtkAction *action,
1456 EmpathyChatWindow *self)
1458 empathy_url_show (self->priv->dialog, "help:empathy");
1462 chat_window_help_about_activate_cb (GtkAction *action,
1463 EmpathyChatWindow *self)
1465 empathy_about_dialog_new (GTK_WINDOW (self->priv->dialog));
1469 chat_window_delete_event_cb (GtkWidget *dialog,
1471 EmpathyChatWindow *self)
1473 EmpathyChat *chat = NULL;
1477 DEBUG ("Delete event received");
1479 for (l = self->priv->chats; l != NULL; l = l->next)
1481 if (chat_needs_close_confirmation (l->data))
1490 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1494 remove_all_chats (self);
1501 chat_window_composing_cb (EmpathyChat *chat,
1502 gboolean is_composing,
1503 EmpathyChatWindow *self)
1505 chat_window_update_chat_tab (chat);
1509 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1512 gtk_window_set_urgency_hint (GTK_WINDOW (self->priv->dialog), urgent);
1516 chat_window_notification_closed_cb (NotifyNotification *notify,
1517 EmpathyChatWindow *self)
1519 g_object_unref (notify);
1520 if (self->priv->notification == notify)
1521 self->priv->notification = NULL;
1525 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1526 EmpathyMessage *message,
1529 EmpathyContact *sender;
1530 const gchar *header;
1534 gboolean res, has_x_canonical_append;
1535 NotifyNotification *notification = self->priv->notification;
1537 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1540 res = g_settings_get_boolean (self->priv->gsettings_notif,
1541 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1546 sender = empathy_message_get_sender (message);
1547 header = empathy_contact_get_alias (sender);
1548 body = empathy_message_get_body (message);
1549 escaped = g_markup_escape_text (body, -1);
1551 has_x_canonical_append = empathy_notify_manager_has_capability (
1552 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1554 if (notification != NULL && !has_x_canonical_append)
1556 /* if the notification server supports x-canonical-append, it is
1557 better to not use notify_notification_update to avoid
1558 overwriting the current notification message */
1559 notify_notification_update (notification,
1560 header, escaped, NULL);
1564 /* if the notification server supports x-canonical-append,
1565 the hint will be added, so that the message from the
1566 just created notification will be automatically appended
1567 to an existing notification with the same title.
1568 In this way the previous message will not be lost: the new
1569 message will appear below it, in the same notification */
1570 const gchar *category = empathy_chat_is_room (chat)
1571 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1572 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1573 notification = notify_notification_new (header, escaped, NULL);
1575 if (self->priv->notification == NULL)
1576 self->priv->notification = notification;
1578 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1580 tp_g_signal_connect_object (notification, "closed",
1581 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1583 if (has_x_canonical_append)
1585 /* We have to set a not empty string to keep libnotify happy */
1586 notify_notification_set_hint_string (notification,
1587 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1590 notify_notification_set_hint (notification,
1591 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1594 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1595 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1599 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1600 g_object_unref (pixbuf);
1603 notify_notification_show (notification, NULL);
1609 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1613 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1615 g_object_get (self->priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1621 chat_window_new_message_cb (EmpathyChat *chat,
1622 EmpathyMessage *message,
1624 gboolean should_highlight,
1625 EmpathyChatWindow *self)
1628 gboolean needs_urgency;
1629 EmpathyContact *sender;
1631 has_focus = empathy_chat_window_has_focus (self);
1633 /* - if we're the sender, we play the sound if it's specified in the
1634 * preferences and we're not away.
1635 * - if we receive a message, we play the sound if it's specified in the
1636 * preferences and the window does not have focus on the chat receiving
1640 sender = empathy_message_get_sender (message);
1642 if (empathy_contact_is_user (sender))
1644 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self->priv->dialog),
1645 EMPATHY_SOUND_MESSAGE_OUTGOING);
1649 if (has_focus && self->priv->current_chat == chat)
1651 /* window and tab are focused so consider the message to be read */
1653 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1654 empathy_chat_messages_read (chat);
1658 /* Update the chat tab if this is the first unread message */
1659 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1661 chat_window_update_chat_tab (chat);
1664 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1665 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1666 * an unamed MUC (msn-like).
1667 * In case of a MUC, we set urgency if either:
1668 * a) the chatroom's always_urgent property is TRUE
1669 * b) the message contains our alias
1671 if (empathy_chat_is_room (chat))
1675 EmpathyChatroom *chatroom;
1677 account = empathy_chat_get_account (chat);
1678 room = empathy_chat_get_id (chat);
1680 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1683 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1684 needs_urgency = TRUE;
1686 needs_urgency = should_highlight;
1690 needs_urgency = TRUE;
1696 chat_window_set_urgency_hint (self, TRUE);
1698 /* Pending messages have already been displayed and notified in the
1699 * approver, so we don't display a notification and play a sound
1703 empathy_sound_manager_play (self->priv->sound_mgr,
1704 GTK_WIDGET (self->priv->dialog), EMPATHY_SOUND_MESSAGE_INCOMING);
1706 chat_window_show_or_update_notification (self, message, chat);
1710 /* update the number of unread messages and the window icon */
1711 chat_window_title_update (self);
1712 chat_window_icon_update (self, TRUE);
1716 chat_window_command_part (EmpathyChat *chat,
1719 EmpathyChat *chat_to_be_parted;
1720 EmpathyTpChat *tp_chat = NULL;
1722 if (strv[1] == NULL)
1724 /* No chatroom ID specified */
1725 tp_chat = empathy_chat_get_tp_chat (chat);
1728 empathy_tp_chat_leave (tp_chat, "");
1733 chat_to_be_parted = empathy_chat_window_find_chat (
1734 empathy_chat_get_account (chat), strv[1], FALSE);
1736 if (chat_to_be_parted != NULL)
1738 /* Found a chatroom matching the specified ID */
1739 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1742 empathy_tp_chat_leave (tp_chat, strv[2]);
1748 /* Going by the syntax of PART command:
1750 * /PART [<chatroom-ID>] [<reason>]
1752 * Chatroom-ID is not a must to specify a reason.
1753 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1754 * MUC then the current chatroom should be parted and srtv[1] should
1755 * be treated as part of the optional part-message. */
1756 message = g_strconcat (strv[1], " ", strv[2], NULL);
1757 tp_chat = empathy_chat_get_tp_chat (chat);
1760 empathy_tp_chat_leave (tp_chat, message);
1766 static GtkNotebook *
1767 notebook_create_window_cb (GtkNotebook *source,
1773 EmpathyChatWindow *window, *new_window;
1776 chat = EMPATHY_CHAT (page);
1777 window = chat_window_find_chat (chat);
1779 new_window = empathy_chat_window_new ();
1781 DEBUG ("Detach hook called");
1783 empathy_chat_window_move_chat (window, new_window, chat);
1785 gtk_widget_show (new_window->priv->dialog);
1786 gtk_window_move (GTK_WINDOW (new_window->priv->dialog), x, y);
1792 chat_window_page_switched_cb (GtkNotebook *notebook,
1795 EmpathyChatWindow *self)
1797 EmpathyChat *chat = EMPATHY_CHAT (child);
1799 DEBUG ("Page switched");
1801 if (self->priv->page_added)
1803 self->priv->page_added = FALSE;
1804 empathy_chat_scroll_down (chat);
1806 else if (self->priv->current_chat == chat)
1811 self->priv->current_chat = chat;
1812 empathy_chat_messages_read (chat);
1814 chat_window_update_chat_tab (chat);
1818 chat_window_page_added_cb (GtkNotebook *notebook,
1821 EmpathyChatWindow *self)
1825 /* If we just received DND to the same window, we don't want
1826 * to do anything here like removing the tab and then readding
1827 * it, so we return here and in "page-added".
1829 if (self->priv->dnd_same_window)
1831 DEBUG ("Page added (back to the same window)");
1832 self->priv->dnd_same_window = FALSE;
1836 DEBUG ("Page added");
1838 /* Get chat object */
1839 chat = EMPATHY_CHAT (child);
1841 /* Connect chat signals for this window */
1842 g_signal_connect (chat, "composing",
1843 G_CALLBACK (chat_window_composing_cb), self);
1844 g_signal_connect (chat, "new-message",
1845 G_CALLBACK (chat_window_new_message_cb), self);
1846 g_signal_connect (chat, "part-command-entered",
1847 G_CALLBACK (chat_window_command_part), NULL);
1848 g_signal_connect (chat, "notify::tp-chat",
1849 G_CALLBACK (chat_window_update_chat_tab), self);
1851 /* Set flag so we know to perform some special operations on
1852 * switch page due to the new page being added.
1854 self->priv->page_added = TRUE;
1856 /* Get list of chats up to date */
1857 self->priv->chats = g_list_append (self->priv->chats, chat);
1859 chat_window_update_chat_tab (chat);
1863 chat_window_page_removed_cb (GtkNotebook *notebook,
1866 EmpathyChatWindow *self)
1870 /* If we just received DND to the same window, we don't want
1871 * to do anything here like removing the tab and then readding
1872 * it, so we return here and in "page-added".
1874 if (self->priv->dnd_same_window)
1876 DEBUG ("Page removed (and will be readded to same window)");
1880 DEBUG ("Page removed");
1882 /* Get chat object */
1883 chat = EMPATHY_CHAT (child);
1885 /* Disconnect all signal handlers for this chat and this window */
1886 g_signal_handlers_disconnect_by_func (chat,
1887 G_CALLBACK (chat_window_composing_cb), self);
1888 g_signal_handlers_disconnect_by_func (chat,
1889 G_CALLBACK (chat_window_new_message_cb), self);
1890 g_signal_handlers_disconnect_by_func (chat,
1891 G_CALLBACK (chat_window_update_chat_tab), self);
1893 /* Keep list of chats up to date */
1894 self->priv->chats = g_list_remove (self->priv->chats, chat);
1895 empathy_chat_messages_read (chat);
1897 if (self->priv->chats == NULL)
1899 g_object_unref (self);
1903 chat_window_update (self, TRUE);
1908 chat_window_focus_in_event_cb (GtkWidget *widget,
1910 EmpathyChatWindow *self)
1912 empathy_chat_messages_read (self->priv->current_chat);
1914 chat_window_set_urgency_hint (self, FALSE);
1916 /* Update the title, since we now mark all unread messages as read. */
1917 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1923 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1924 EmpathyChatWindow *self)
1926 chat_window_contact_menu_update (self);
1930 chat_window_focus_out_event_cb (GtkWidget *widget,
1932 EmpathyChatWindow *self)
1934 if (self->priv->individual_mgr != NULL)
1937 /* Keep the individual manager alive so we won't fetch everything from Folks
1938 * each time we need to use it. Loading FolksAggregator can takes quite a
1939 * while (if user has a huge LDAP abook for example) and it blocks
1940 * the mainloop during most of this loading. We workaround this by loading
1941 * it when the chat window has been unfocused and so, hopefully, not impact
1942 * the reactivity of the chat window too much.
1944 * The individual manager (and so Folks) is needed to know to which
1945 * FolksIndividual a TpContact belongs, including:
1946 * - empathy_chat_get_contact_menu: to list all the personas of the contact
1947 * - empathy_display_individual_info: to invoke gnome-contacts with the
1948 * FolksIndividual.id of the contact
1949 * - drag_data_received_individual_id: to find the individual associated
1950 * with the ID we received from the DnD in order to invite him.
1952 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
1954 if (!empathy_individual_manager_get_contacts_loaded (
1955 self->priv->individual_mgr))
1957 /* We want to update the contact menu when Folks is loaded so we can
1958 * list all the personas of the contact. */
1959 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
1960 G_CALLBACK (contacts_loaded_cb), self, 0);
1963 g_object_notify (G_OBJECT (self), "individual-manager");
1969 chat_window_drag_drop (GtkWidget *widget,
1970 GdkDragContext *context,
1974 EmpathyChatWindow *self)
1978 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1979 if (target == GDK_NONE)
1980 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1982 if (target != GDK_NONE)
1984 gtk_drag_get_data (widget, context, target, time_);
1992 chat_window_drag_motion (GtkWidget *widget,
1993 GdkDragContext *context,
1997 EmpathyChatWindow *self)
2001 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2003 if (target != GDK_NONE)
2005 /* This is a file drag. Ensure the contact is online and set the
2006 drag type to COPY. Note that it's possible that the tab will
2007 be switched by GTK+ after a timeout from drag_motion without
2008 getting another drag_motion to disable the drop. You have
2009 to hold your mouse really still.
2011 EmpathyContact *contact;
2013 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2015 /* contact is NULL for multi-user chats. We don't do
2016 * file transfers to MUCs. We also don't send files
2017 * to offline contacts or contacts that don't support
2020 if ((contact == NULL) || !empathy_contact_is_online (contact))
2022 gdk_drag_status (context, 0, time_);
2026 if (!(empathy_contact_get_capabilities (contact)
2027 & EMPATHY_CAPABILITIES_FT))
2029 gdk_drag_status (context, 0, time_);
2033 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2037 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2038 if (target != GDK_NONE)
2040 /* This is a drag of a contact from a contact list. Set to COPY.
2041 FIXME: If this drag is to a MUC window, it invites the user.
2042 Otherwise, it opens a chat. Should we use a different drag
2043 type for invites? Should we allow ASK?
2045 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2053 drag_data_received_individual_id (EmpathyChatWindow *self,
2055 GdkDragContext *context,
2058 GtkSelectionData *selection,
2063 FolksIndividual *individual;
2064 EmpathyTpChat *chat;
2065 TpContact *tp_contact;
2067 EmpathyContact *contact;
2069 id = (const gchar *) gtk_selection_data_get_data (selection);
2071 DEBUG ("DND invididual %s", id);
2073 if (self->priv->current_chat == NULL)
2076 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2080 if (!empathy_tp_chat_can_add_contact (chat))
2082 DEBUG ("Can't invite contact to %s",
2083 tp_proxy_get_object_path (chat));
2087 if (self->priv->individual_mgr == NULL)
2088 /* Not likely as we have to focus out the chat window in order to start
2089 * the DnD but best to be safe. */
2092 individual = empathy_individual_manager_lookup_member (
2093 self->priv->individual_mgr, id);
2094 if (individual == NULL)
2096 DEBUG ("Failed to find individual %s", id);
2100 conn = tp_channel_get_connection ((TpChannel *) chat);
2101 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2102 if (tp_contact == NULL)
2104 DEBUG ("Can't find a TpContact on connection %s for %s",
2105 tp_proxy_get_object_path (conn), id);
2109 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2110 tp_channel_get_identifier ((TpChannel *) chat));
2112 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2113 empathy_tp_chat_add (chat, contact, NULL);
2114 g_object_unref (contact);
2117 gtk_drag_finish (context, TRUE, FALSE, time_);
2121 chat_window_drag_data_received (GtkWidget *widget,
2122 GdkDragContext *context,
2125 GtkSelectionData *selection,
2128 EmpathyChatWindow *self)
2130 if (info == DND_DRAG_TYPE_CONTACT_ID)
2132 EmpathyChat *chat = NULL;
2133 EmpathyChatWindow *old_window;
2134 TpAccount *account = NULL;
2135 EmpathyClientFactory *factory;
2138 const gchar *account_id;
2139 const gchar *contact_id;
2141 id = (const gchar*) gtk_selection_data_get_data (selection);
2143 factory = empathy_client_factory_dup ();
2145 DEBUG ("DND contact from roster with id:'%s'", id);
2147 strv = g_strsplit (id, ":", 2);
2148 if (g_strv_length (strv) == 2)
2150 account_id = strv[0];
2151 contact_id = strv[1];
2153 account = tp_simple_client_factory_ensure_account (
2154 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2156 g_object_unref (factory);
2157 if (account != NULL)
2158 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2161 if (account == NULL)
2164 gtk_drag_finish (context, FALSE, FALSE, time_);
2170 empathy_chat_with_contact_id (account, contact_id,
2171 empathy_get_current_action_time (), NULL, NULL);
2179 old_window = chat_window_find_chat (chat);
2182 if (old_window == self)
2184 gtk_drag_finish (context, TRUE, FALSE, time_);
2188 empathy_chat_window_move_chat (old_window, self, chat);
2192 empathy_chat_window_add_chat (self, chat);
2195 /* Added to take care of any outstanding chat events */
2196 empathy_chat_window_present_chat (chat,
2197 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2199 /* We should return TRUE to remove the data when doing
2200 * GDK_ACTION_MOVE, but we don't here otherwise it has
2201 * weird consequences, and we handle that internally
2202 * anyway with add_chat () and remove_chat ().
2204 gtk_drag_finish (context, TRUE, FALSE, time_);
2206 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2208 drag_data_received_individual_id (self, widget, context, x, y,
2209 selection, info, time_);
2211 else if (info == DND_DRAG_TYPE_URI_LIST)
2213 EmpathyContact *contact;
2216 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2218 /* contact is NULL when current_chat is a multi-user chat.
2219 * We don't do file transfers to MUCs, so just cancel the drag.
2221 if (contact == NULL)
2223 gtk_drag_finish (context, TRUE, FALSE, time_);
2227 data = (const gchar *) gtk_selection_data_get_data (selection);
2228 empathy_send_file_from_uri_list (contact, data);
2230 gtk_drag_finish (context, TRUE, FALSE, time_);
2232 else if (info == DND_DRAG_TYPE_TAB)
2235 EmpathyChatWindow *old_window = NULL;
2239 chat = (void *) gtk_selection_data_get_data (selection);
2240 old_window = chat_window_find_chat (*chat);
2244 self->priv->dnd_same_window = (old_window == self);
2246 DEBUG ("DND tab (within same window: %s)",
2247 self->priv->dnd_same_window ? "Yes" : "No");
2252 DEBUG ("DND from unknown source");
2253 gtk_drag_finish (context, FALSE, FALSE, time_);
2258 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2259 guint num_chats_in_manager,
2260 EmpathyChatWindow *self)
2262 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2263 num_chats_in_manager > 0);
2267 chat_window_finalize (GObject *object)
2269 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2271 DEBUG ("Finalized: %p", object);
2273 g_object_unref (self->priv->ui_manager);
2274 g_object_unref (self->priv->chatroom_manager);
2275 g_object_unref (self->priv->notify_mgr);
2276 g_object_unref (self->priv->gsettings_chat);
2277 g_object_unref (self->priv->gsettings_notif);
2278 g_object_unref (self->priv->gsettings_ui);
2279 g_object_unref (self->priv->sound_mgr);
2280 g_clear_object (&self->priv->individual_mgr);
2282 if (self->priv->notification != NULL)
2284 notify_notification_close (self->priv->notification, NULL);
2285 self->priv->notification = NULL;
2288 if (self->priv->contact_targets)
2289 gtk_target_list_unref (self->priv->contact_targets);
2291 if (self->priv->file_targets)
2292 gtk_target_list_unref (self->priv->file_targets);
2294 if (self->priv->chat_manager)
2296 g_signal_handler_disconnect (self->priv->chat_manager,
2297 self->priv->chat_manager_chats_changed_id);
2298 g_object_unref (self->priv->chat_manager);
2299 self->priv->chat_manager = NULL;
2302 chat_windows = g_list_remove (chat_windows, self);
2303 gtk_widget_destroy (self->priv->dialog);
2305 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2309 chat_window_get_property (GObject *object,
2314 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2316 switch (property_id)
2318 case PROP_INDIVIDUAL_MGR:
2319 g_value_set_object (value, self->priv->individual_mgr);
2321 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2327 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2329 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2332 object_class->get_property = chat_window_get_property;
2333 object_class->finalize = chat_window_finalize;
2335 spec = g_param_spec_object ("individual-manager", "individual-manager",
2336 "EmpathyIndividualManager",
2337 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2338 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2339 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2341 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2345 empathy_chat_window_init (EmpathyChatWindow *self)
2348 GtkAccelGroup *accel_group;
2353 GtkWidget *chat_vbox;
2355 EmpathySmileyManager *smiley_manager;
2357 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2358 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2360 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2361 gui = empathy_builder_get_file (filename,
2362 "chat_window", &self->priv->dialog,
2363 "chat_vbox", &chat_vbox,
2364 "ui_manager", &self->priv->ui_manager,
2365 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2366 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2367 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2368 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2369 "menu_edit_cut", &self->priv->menu_edit_cut,
2370 "menu_edit_copy", &self->priv->menu_edit_copy,
2371 "menu_edit_paste", &self->priv->menu_edit_paste,
2372 "menu_edit_find", &self->priv->menu_edit_find,
2373 "menu_tabs_next", &self->priv->menu_tabs_next,
2374 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2375 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2376 "menu_tabs_left", &self->priv->menu_tabs_left,
2377 "menu_tabs_right", &self->priv->menu_tabs_right,
2378 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2382 empathy_builder_connect (gui, self,
2383 "menu_conv", "activate", chat_window_conv_activate_cb,
2384 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2385 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2386 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2387 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2388 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2389 "menu_conv_close", "activate", chat_window_close_activate_cb,
2390 "menu_edit", "activate", chat_window_edit_activate_cb,
2391 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2392 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2393 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2394 "menu_edit_find", "activate", chat_window_find_activate_cb,
2395 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2396 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2397 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2398 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2399 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2400 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2401 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2402 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2405 g_object_ref (self->priv->ui_manager);
2406 g_object_unref (gui);
2408 empathy_set_css_provider (GTK_WIDGET (self->priv->dialog));
2410 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2411 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2412 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2413 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2415 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2417 self->priv->notebook = gtk_notebook_new ();
2419 g_signal_connect (self->priv->notebook, "create-window",
2420 G_CALLBACK (notebook_create_window_cb), self);
2422 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2423 "EmpathyChatWindow");
2424 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2425 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2426 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2427 gtk_widget_show (self->priv->notebook);
2430 accel_group = gtk_accel_group_new ();
2431 gtk_window_add_accel_group (GTK_WINDOW (self->priv->dialog), accel_group);
2433 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2435 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2438 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2442 g_object_unref (accel_group);
2444 /* Set up drag target lists */
2445 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2446 G_N_ELEMENTS (drag_types_dest_contact));
2448 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2449 G_N_ELEMENTS (drag_types_dest_file));
2451 /* Set up smiley menu */
2452 smiley_manager = empathy_smiley_manager_dup_singleton ();
2453 submenu = empathy_smiley_menu_new (smiley_manager,
2454 chat_window_insert_smiley_activate_cb, self);
2456 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2457 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2458 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2459 g_object_unref (smiley_manager);
2461 /* Set up signals we can't do with ui file since we may need to
2462 * block/unblock them at some later stage.
2465 g_signal_connect (self->priv->dialog, "delete_event",
2466 G_CALLBACK (chat_window_delete_event_cb), self);
2467 g_signal_connect (self->priv->dialog, "focus_in_event",
2468 G_CALLBACK (chat_window_focus_in_event_cb), self);
2469 g_signal_connect (self->priv->dialog, "focus_out_event",
2470 G_CALLBACK (chat_window_focus_out_event_cb), self);
2471 g_signal_connect_after (self->priv->notebook, "switch_page",
2472 G_CALLBACK (chat_window_page_switched_cb), self);
2473 g_signal_connect (self->priv->notebook, "page_added",
2474 G_CALLBACK (chat_window_page_added_cb), self);
2475 g_signal_connect (self->priv->notebook, "page_removed",
2476 G_CALLBACK (chat_window_page_removed_cb), self);
2478 /* Set up drag and drop */
2479 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2480 GTK_DEST_DEFAULT_HIGHLIGHT,
2482 G_N_ELEMENTS (drag_types_dest),
2483 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2485 /* connect_after to allow GtkNotebook's built-in tab switching */
2486 g_signal_connect_after (self->priv->notebook, "drag-motion",
2487 G_CALLBACK (chat_window_drag_motion), self);
2488 g_signal_connect (self->priv->notebook, "drag-data-received",
2489 G_CALLBACK (chat_window_drag_data_received), self);
2490 g_signal_connect (self->priv->notebook, "drag-drop",
2491 G_CALLBACK (chat_window_drag_drop), self);
2493 chat_windows = g_list_prepend (chat_windows, self);
2495 /* Set up private details */
2496 self->priv->chats = NULL;
2497 self->priv->current_chat = NULL;
2498 self->priv->notification = NULL;
2500 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2502 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2503 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2504 self->priv->chat_manager, "closed-chats-changed",
2505 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2507 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2508 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2511 /* Returns the window to open a new tab in if there is a suitable window,
2512 * otherwise, returns NULL indicating that a new window should be added.
2514 static EmpathyChatWindow *
2515 empathy_chat_window_get_default (gboolean room)
2517 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2519 gboolean separate_windows = TRUE;
2521 separate_windows = g_settings_get_boolean (gsettings,
2522 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2524 g_object_unref (gsettings);
2526 if (separate_windows)
2527 /* Always create a new window */
2530 for (l = chat_windows; l; l = l->next)
2532 EmpathyChatWindow *chat_window;
2533 guint nb_rooms, nb_private;
2535 chat_window = l->data;
2537 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2539 /* Skip the window if there aren't any rooms in it */
2540 if (room && nb_rooms == 0)
2543 /* Skip the window if there aren't any 1-1 chats in it */
2544 if (!room && nb_private == 0)
2554 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2558 GtkWidget *popup_label;
2560 GValue value = { 0, };
2562 g_return_if_fail (self != NULL);
2563 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2565 /* Reference the chat object */
2566 g_object_ref (chat);
2568 /* If this window has just been created, position it */
2569 if (self->priv->chats == NULL)
2571 const gchar *name = "chat-window";
2572 gboolean separate_windows;
2574 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2575 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2577 if (empathy_chat_is_room (chat))
2578 name = "room-window";
2580 if (separate_windows)
2584 /* Save current position of the window */
2585 gtk_window_get_position (GTK_WINDOW (self->priv->dialog), &x, &y);
2587 /* First bind to the 'generic' name. So new window for which we didn't
2588 * save a geometry yet will have the geometry of the last saved
2589 * window (bgo #601191). */
2590 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2592 /* Restore previous position of the window so the newly created window
2593 * won't be in the same position as the latest saved window and so
2594 * completely hide it. */
2595 gtk_window_move (GTK_WINDOW (self->priv->dialog), x, y);
2597 /* Then bind it to the name of the contact/room so we'll save the
2598 * geometry specific to this window */
2599 name = empathy_chat_get_id (chat);
2602 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2605 child = GTK_WIDGET (chat);
2606 label = chat_window_create_label (self, chat, TRUE);
2607 popup_label = chat_window_create_label (self, chat, FALSE);
2608 gtk_widget_show (child);
2610 g_signal_connect (chat, "notify::name",
2611 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2612 g_signal_connect (chat, "notify::subject",
2613 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2614 g_signal_connect (chat, "notify::remote-contact",
2615 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2616 g_signal_connect (chat, "notify::sms-channel",
2617 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2618 g_signal_connect (chat, "notify::n-messages-sending",
2619 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2620 g_signal_connect (chat, "notify::nb-unread-messages",
2621 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2622 chat_window_chat_notify_cb (chat);
2624 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2626 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2627 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2628 g_value_init (&value, G_TYPE_BOOLEAN);
2629 g_value_set_boolean (&value, TRUE);
2630 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2631 child, "tab-expand" , &value);
2632 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2633 child, "tab-fill" , &value);
2634 g_value_unset (&value);
2636 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2640 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2644 EmpathyContact *remote_contact;
2645 EmpathyChatManager *chat_manager;
2647 g_return_if_fail (self != NULL);
2648 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2650 g_signal_handlers_disconnect_by_func (chat,
2651 chat_window_chat_notify_cb, NULL);
2653 remote_contact = g_object_get_data (G_OBJECT (chat),
2654 "chat-window-remote-contact");
2658 g_signal_handlers_disconnect_by_func (remote_contact,
2659 chat_window_update_chat_tab, chat);
2662 chat_manager = empathy_chat_manager_dup_singleton ();
2663 empathy_chat_manager_closed_chat (chat_manager, chat);
2664 g_object_unref (chat_manager);
2666 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2668 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2670 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2672 g_object_unref (chat);
2676 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2677 EmpathyChatWindow *new_window,
2682 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2683 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2684 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2686 widget = GTK_WIDGET (chat);
2688 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2689 G_OBJECT (widget)->ref_count);
2691 /* We reference here to make sure we don't loose the widget
2692 * and the EmpathyChat object during the move.
2694 g_object_ref (chat);
2695 g_object_ref (widget);
2697 empathy_chat_window_remove_chat (old_window, chat);
2698 empathy_chat_window_add_chat (new_window, chat);
2700 g_object_unref (widget);
2701 g_object_unref (chat);
2705 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2710 g_return_if_fail (self != NULL);
2711 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2713 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2716 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2721 empathy_chat_window_find_chat (TpAccount *account,
2723 gboolean sms_channel)
2727 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2729 for (l = chat_windows; l; l = l->next)
2731 EmpathyChatWindow *window = l->data;
2734 for (ll = window->priv->chats; ll; ll = ll->next)
2740 if (account == empathy_chat_get_account (chat) &&
2741 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2742 sms_channel == empathy_chat_is_sms_channel (chat))
2751 empathy_chat_window_present_chat (EmpathyChat *chat,
2754 EmpathyChatWindow *self;
2755 guint32 x_timestamp;
2757 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2759 self = chat_window_find_chat (chat);
2761 /* If the chat has no window, create one */
2764 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2767 self = empathy_chat_window_new ();
2769 /* we want to display the newly created window even if we
2770 * don't present it */
2771 gtk_widget_show (self->priv->dialog);
2774 empathy_chat_window_add_chat (self, chat);
2777 /* Don't force the window to show itself when it wasn't
2778 * an action by the user
2780 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2783 if (x_timestamp != GDK_CURRENT_TIME)
2785 /* Don't present or switch tab if the action was earlier than the
2786 * last actions X time, accounting for overflow and the first ever
2789 if (self->priv->x_user_action_time != 0
2790 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2793 self->priv->x_user_action_time = x_timestamp;
2796 empathy_chat_window_switch_to_chat (self, chat);
2798 /* Don't use empathy_window_present_with_time () which would move the window
2799 * to our current desktop but move to the window's desktop instead. This is
2800 * more coherent with Shell's 'app is ready' notication which moves the view
2801 * to the app desktop rather than moving the app itself. */
2802 empathy_move_to_window_desktop (GTK_WINDOW (self->priv->dialog), x_timestamp);
2804 gtk_widget_grab_focus (chat->input_text_view);
2809 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2814 guint _nb_rooms = 0, _nb_private = 0;
2816 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2818 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2824 if (nb_rooms != NULL)
2825 *nb_rooms = _nb_rooms;
2826 if (nb_private != NULL)
2827 *nb_private = _nb_private;
2830 EmpathyIndividualManager *
2831 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2833 return self->priv->individual_mgr;