2 * Copyright (C) 2003-2007 Imendio AB
3 * Copyright (C) 2007-2012 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Richard Hult <richard@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24 * Xavier Claessens <xclaesse@gmail.com>
25 * Rômulo Fernandes Machado <romulo@castorgroup.net>
33 #include <gdk/gdkkeysyms.h>
35 #include <glib/gi18n.h>
36 #include <libnotify/notification.h>
38 #include <libempathy/empathy-client-factory.h>
39 #include <libempathy/empathy-contact.h>
40 #include <libempathy/empathy-message.h>
41 #include <libempathy/empathy-chatroom-manager.h>
42 #include <libempathy/empathy-gsettings.h>
43 #include <libempathy/empathy-utils.h>
44 #include <libempathy/empathy-request-util.h>
45 #include <libempathy/empathy-individual-manager.h>
47 #include <libempathy-gtk/empathy-images.h>
48 #include <libempathy-gtk/empathy-log-window.h>
49 #include <libempathy-gtk/empathy-geometry.h>
50 #include <libempathy-gtk/empathy-smiley-manager.h>
51 #include <libempathy-gtk/empathy-sound-manager.h>
52 #include <libempathy-gtk/empathy-ui-utils.h>
53 #include <libempathy-gtk/empathy-notify-manager.h>
55 #include "empathy-chat-manager.h"
56 #include "empathy-chat-window.h"
57 #include "empathy-about-dialog.h"
58 #include "empathy-invite-participant-dialog.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
61 #include <libempathy/empathy-debug.h>
63 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
65 #define X_EARLIER_OR_EQL(t1, t2) \
66 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
67 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
70 struct _EmpathyChatWindowPriv
72 EmpathyChat *current_chat;
75 gboolean dnd_same_window;
76 EmpathyChatroomManager *chatroom_manager;
77 EmpathyNotifyManager *notify_mgr;
80 NotifyNotification *notification;
82 GtkTargetList *contact_targets;
83 GtkTargetList *file_targets;
85 EmpathyChatManager *chat_manager;
86 gulong chat_manager_chats_changed_id;
89 GtkUIManager *ui_manager;
90 GtkAction *menu_conv_insert_smiley;
91 GtkAction *menu_conv_favorite;
92 GtkAction *menu_conv_always_urgent;
93 GtkAction *menu_conv_toggle_contacts;
95 GtkAction *menu_edit_cut;
96 GtkAction *menu_edit_copy;
97 GtkAction *menu_edit_paste;
98 GtkAction *menu_edit_find;
100 GtkAction *menu_tabs_next;
101 GtkAction *menu_tabs_prev;
102 GtkAction *menu_tabs_undo_close_tab;
103 GtkAction *menu_tabs_left;
104 GtkAction *menu_tabs_right;
105 GtkAction *menu_tabs_detach;
107 /* Last user action time we acted upon to show a tab */
108 guint32 x_user_action_time;
110 GSettings *gsettings_chat;
111 GSettings *gsettings_notif;
112 GSettings *gsettings_ui;
114 EmpathySoundManager *sound_mgr;
116 gboolean updating_menu;
119 static GList *chat_windows = NULL;
121 static const guint tab_accel_keys[] =
123 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
124 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
129 DND_DRAG_TYPE_CONTACT_ID,
130 DND_DRAG_TYPE_INDIVIDUAL_ID,
131 DND_DRAG_TYPE_URI_LIST,
135 static const GtkTargetEntry drag_types_dest[] =
137 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
138 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
139 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
140 /* FIXME: disabled because of bug #640513
141 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
142 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
146 static const GtkTargetEntry drag_types_dest_contact[] =
148 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
149 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
152 static const GtkTargetEntry drag_types_dest_file[] =
154 /* must be first to be prioritized, in order to receive the
155 * note's file path from Tomboy instead of an URI */
156 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
157 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
160 static void chat_window_update (EmpathyChatWindow *window,
161 gboolean update_contact_menu);
163 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
166 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
169 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
170 EmpathyChatWindow *new_window,
173 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
177 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT)
180 chat_window_accel_cb (GtkAccelGroup *accelgroup,
184 EmpathyChatWindow *self)
189 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
191 if (tab_accel_keys[i] == key)
199 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
202 static EmpathyChatWindow *
203 chat_window_find_chat (EmpathyChat *chat)
207 for (l = chat_windows; l; l = l->next)
209 EmpathyChatWindow *window = l->data;
211 ll = g_list_find (window->priv->chats, chat);
220 remove_all_chats (EmpathyChatWindow *self)
224 while (self->priv->chats)
225 empathy_chat_window_remove_chat (self, self->priv->chats->data);
227 g_object_unref (self);
231 confirm_close_response_cb (GtkWidget *dialog,
233 EmpathyChatWindow *window)
237 chat = g_object_get_data (G_OBJECT (dialog), "chat");
239 gtk_widget_destroy (dialog);
241 if (response != GTK_RESPONSE_ACCEPT)
245 empathy_chat_window_remove_chat (window, chat);
247 remove_all_chats (window);
251 confirm_close (EmpathyChatWindow *self,
252 gboolean close_window,
257 gchar *primary, *secondary;
259 g_return_if_fail (n_rooms > 0);
262 g_return_if_fail (chat == NULL);
264 g_return_if_fail (chat != NULL);
266 /* If there are no chats in this window, how could we possibly have got
269 g_return_if_fail (self->priv->chats != NULL);
271 /* Treat closing a window which only has one tab exactly like closing
274 if (close_window && self->priv->chats->next == NULL)
276 close_window = FALSE;
277 chat = self->priv->chats->data;
282 primary = g_strdup (_("Close this window?"));
286 gchar *chat_name = empathy_chat_dup_name (chat);
287 secondary = g_strdup_printf (
288 _("Closing this window will leave %s. You will "
289 "not receive any further messages until you "
296 secondary = g_strdup_printf (
297 /* Note to translators: the number of chats will
298 * always be at least 2.
301 "Closing this window will leave a chat room. You will "
302 "not receive any further messages until you rejoin it.",
303 "Closing this window will leave %u chat rooms. You will "
304 "not receive any further messages until you rejoin them.",
311 gchar *chat_name = empathy_chat_dup_name (chat);
312 primary = g_strdup_printf (_("Leave %s?"), chat_name);
313 secondary = g_strdup (
314 _("You will not receive any further messages from this chat "
315 "room until you rejoin it."));
319 dialog = gtk_message_dialog_new (
320 GTK_WINDOW (self->priv->dialog),
321 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
326 gtk_window_set_title (GTK_WINDOW (dialog), "");
327 g_object_set (dialog, "secondary-text", secondary, NULL);
332 gtk_dialog_add_button (GTK_DIALOG (dialog),
333 close_window ? _("Close window") : _("Leave room"),
334 GTK_RESPONSE_ACCEPT);
335 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
336 GTK_RESPONSE_ACCEPT);
339 g_object_set_data (G_OBJECT (dialog), "chat", chat);
341 g_signal_connect (dialog, "response",
342 G_CALLBACK (confirm_close_response_cb), self);
344 gtk_window_present (GTK_WINDOW (dialog));
347 /* Returns TRUE if we should check if the user really wants to leave. If it's
348 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
349 * the user is actually in the room as opposed to having been kicked or gone
350 * offline or something), then we should check.
353 chat_needs_close_confirmation (EmpathyChat *chat)
355 return (empathy_chat_is_room (chat) &&
356 empathy_chat_get_tp_chat (chat) != NULL);
360 maybe_close_chat (EmpathyChatWindow *window,
363 g_return_if_fail (chat != NULL);
365 if (chat_needs_close_confirmation (chat))
366 confirm_close (window, FALSE, 1, chat);
368 empathy_chat_window_remove_chat (window, chat);
372 chat_window_close_clicked_cb (GtkAction *action,
375 EmpathyChatWindow *window;
377 window = chat_window_find_chat (chat);
378 maybe_close_chat (window, chat);
382 chat_tab_style_updated_cb (GtkWidget *hbox,
386 int char_width, h, w;
387 PangoContext *context;
388 const PangoFontDescription *font_desc;
389 PangoFontMetrics *metrics;
391 button = g_object_get_data (G_OBJECT (user_data),
392 "chat-window-tab-close-button");
393 context = gtk_widget_get_pango_context (hbox);
395 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
396 GTK_STATE_FLAG_NORMAL);
398 metrics = pango_context_get_metrics (context, font_desc,
399 pango_context_get_language (context));
400 char_width = pango_font_metrics_get_approximate_char_width (metrics);
401 pango_font_metrics_unref (metrics);
403 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
404 GTK_ICON_SIZE_MENU, &w, &h);
406 /* Request at least about 12 chars width plus at least space for the status
407 * image and the close button */
408 gtk_widget_set_size_request (hbox,
409 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
411 gtk_widget_set_size_request (button, w, h);
415 create_close_button (void)
417 GtkWidget *button, *image;
418 GtkStyleContext *context;
420 button = gtk_button_new ();
422 context = gtk_widget_get_style_context (button);
423 gtk_style_context_add_class (context, "empathy-tab-close-button");
425 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
426 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
428 /* We don't want focus/keynav for the button to avoid clutter, and
429 * Ctrl-W works anyway.
431 gtk_widget_set_can_focus (button, FALSE);
432 gtk_widget_set_can_default (button, FALSE);
434 image = gtk_image_new_from_icon_name ("window-close-symbolic",
436 gtk_widget_show (image);
438 gtk_container_add (GTK_CONTAINER (button), image);
444 chat_window_create_label (EmpathyChatWindow *window,
446 gboolean is_tab_label)
449 GtkWidget *name_label;
450 GtkWidget *status_image;
451 GtkWidget *event_box;
452 GtkWidget *event_box_hbox;
453 PangoAttrList *attr_list;
454 PangoAttribute *attr;
456 /* The spacing between the button and the label. */
457 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
459 event_box = gtk_event_box_new ();
460 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
462 name_label = gtk_label_new (NULL);
464 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
466 attr_list = pango_attr_list_new ();
467 attr = pango_attr_scale_new (1/1.2);
468 attr->start_index = 0;
469 attr->end_index = -1;
470 pango_attr_list_insert (attr_list, attr);
471 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
472 pango_attr_list_unref (attr_list);
474 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
475 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
476 g_object_set_data (G_OBJECT (chat),
477 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
480 status_image = gtk_image_new ();
482 /* Spacing between the icon and label. */
483 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
485 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
486 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
488 g_object_set_data (G_OBJECT (chat),
489 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
491 g_object_set_data (G_OBJECT (chat),
492 is_tab_label ? "chat-window-tab-tooltip-widget" :
493 "chat-window-menu-tooltip-widget",
496 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
497 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
501 GtkWidget *close_button;
502 GtkWidget *sending_spinner;
504 sending_spinner = gtk_spinner_new ();
506 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
508 g_object_set_data (G_OBJECT (chat),
509 "chat-window-tab-sending-spinner",
512 close_button = create_close_button ();
513 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
516 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
518 g_signal_connect (close_button,
520 G_CALLBACK (chat_window_close_clicked_cb), chat);
522 /* React to theme changes and also setup the size correctly. */
523 g_signal_connect (hbox, "style-updated",
524 G_CALLBACK (chat_tab_style_updated_cb), chat);
527 gtk_widget_show_all (hbox);
533 _submenu_notify_visible_changed_cb (GObject *object,
537 g_signal_handlers_disconnect_by_func (object,
538 _submenu_notify_visible_changed_cb, userdata);
540 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
544 chat_window_menu_context_update (EmpathyChatWindow *self,
549 gboolean wrap_around;
550 gboolean is_connected;
553 page_num = gtk_notebook_get_current_page (
554 GTK_NOTEBOOK (self->priv->notebook));
555 first_page = (page_num == 0);
556 last_page = (page_num == (num_pages - 1));
557 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
559 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
561 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
563 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
565 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
566 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
567 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
568 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
572 chat_window_conversation_menu_update (EmpathyChatWindow *self)
574 EmpathyTpChat *tp_chat;
575 TpConnection *connection;
577 gboolean sensitive = FALSE;
579 g_return_if_fail (self->priv->current_chat != NULL);
581 action = gtk_ui_manager_get_action (self->priv->ui_manager,
582 "/chats_menubar/menu_conv/menu_conv_invite_participant");
583 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
587 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
589 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
590 (tp_connection_get_status (connection, NULL) ==
591 TP_CONNECTION_STATUS_CONNECTED);
594 gtk_action_set_sensitive (action, sensitive);
598 chat_window_contact_menu_update (EmpathyChatWindow *self)
600 GtkWidget *menu, *submenu, *orig_submenu;
602 if (self->priv->updating_menu)
604 self->priv->updating_menu = TRUE;
606 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
607 "/chats_menubar/menu_contact");
608 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
610 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
612 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
616 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
617 g_object_set_data (G_OBJECT (submenu), "window", self->priv->dialog);
619 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
620 gtk_widget_show (menu);
621 gtk_widget_set_sensitive (menu, TRUE);
625 gtk_widget_set_sensitive (menu, FALSE);
630 tp_g_signal_connect_object (orig_submenu,
632 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
635 self->priv->updating_menu = FALSE;
639 get_all_unread_messages (EmpathyChatWindow *self)
644 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
645 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
651 get_window_title_name (EmpathyChatWindow *self)
653 gchar *active_name, *ret;
655 guint current_unread_msgs;
657 nb_chats = g_list_length (self->priv->chats);
658 g_assert (nb_chats > 0);
660 active_name = empathy_chat_dup_name (self->priv->current_chat);
662 current_unread_msgs = empathy_chat_get_nb_unread_messages (
663 self->priv->current_chat);
668 if (current_unread_msgs == 0)
669 ret = g_strdup (active_name);
671 ret = g_strdup_printf (ngettext (
673 "%s (%d unread)", current_unread_msgs),
674 active_name, current_unread_msgs);
678 guint nb_others = nb_chats - 1;
679 guint all_unread_msgs;
681 all_unread_msgs = get_all_unread_messages (self);
683 if (all_unread_msgs == 0)
685 /* no unread message */
686 ret = g_strdup_printf (ngettext (
688 "%s (and %u others)", nb_others),
689 active_name, nb_others);
691 else if (all_unread_msgs == current_unread_msgs)
693 /* unread messages are in the current tab */
694 ret = g_strdup_printf (ngettext (
696 "%s (%d unread)", current_unread_msgs),
697 active_name, current_unread_msgs);
699 else if (current_unread_msgs == 0)
701 /* unread messages are in other tabs */
702 ret = g_strdup_printf (ngettext (
703 "%s (%d unread from others)",
704 "%s (%d unread from others)",
706 active_name, all_unread_msgs);
710 /* unread messages are in all the tabs */
711 ret = g_strdup_printf (ngettext (
712 "%s (%d unread from all)",
713 "%s (%d unread from all)",
715 active_name, all_unread_msgs);
719 g_free (active_name);
725 chat_window_title_update (EmpathyChatWindow *self)
729 name = get_window_title_name (self);
730 gtk_window_set_title (GTK_WINDOW (self->priv->dialog), name);
735 chat_window_icon_update (EmpathyChatWindow *self,
736 gboolean new_messages)
739 EmpathyContact *remote_contact;
740 gboolean avatar_in_icon;
743 n_chats = g_list_length (self->priv->chats);
745 /* Update window icon */
748 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog),
749 EMPATHY_IMAGE_MESSAGE);
753 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
754 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
756 if (n_chats == 1 && avatar_in_icon)
758 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
759 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
761 gtk_window_set_icon (GTK_WINDOW (self->priv->dialog), icon);
764 g_object_unref (icon);
768 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog), NULL);
774 chat_window_close_button_update (EmpathyChatWindow *self,
778 GtkWidget *chat_close_button;
783 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
784 chat_close_button = g_object_get_data (G_OBJECT (chat),
785 "chat-window-tab-close-button");
786 gtk_widget_hide (chat_close_button);
790 for (i=0; i<num_pages; i++)
792 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
793 chat_close_button = g_object_get_data (G_OBJECT (chat),
794 "chat-window-tab-close-button");
795 gtk_widget_show (chat_close_button);
801 chat_window_update (EmpathyChatWindow *self,
802 gboolean update_contact_menu)
806 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
808 /* Update Tab menu */
809 chat_window_menu_context_update (self, num_pages);
811 chat_window_conversation_menu_update (self);
813 /* If this update is due to a focus-in event, we know the menu will be
814 the same as when we last left it, so no work to do. Besides, if we
815 swap out the menu on a focus-in, we may confuse any external global
817 if (update_contact_menu)
819 chat_window_contact_menu_update (self);
822 chat_window_title_update (self);
824 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
826 chat_window_close_button_update (self, num_pages);
830 append_markup_printf (GString *string,
837 va_start (args, format);
839 tmp = g_markup_vprintf_escaped (format, args);
840 g_string_append (string, tmp);
847 chat_window_update_chat_tab_full (EmpathyChat *chat,
848 gboolean update_contact_menu)
850 EmpathyChatWindow *self;
851 EmpathyContact *remote_contact;
855 const gchar *subject;
856 const gchar *status = NULL;
860 const gchar *icon_name;
861 GtkWidget *tab_image;
862 GtkWidget *menu_image;
863 GtkWidget *sending_spinner;
866 self = chat_window_find_chat (chat);
870 /* Get information */
871 name = empathy_chat_dup_name (chat);
872 account = empathy_chat_get_account (chat);
873 subject = empathy_chat_get_subject (chat);
874 remote_contact = empathy_chat_get_remote_contact (chat);
876 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
878 name, tp_proxy_get_object_path (account), subject, remote_contact);
880 /* Update tab image */
881 if (empathy_chat_get_tp_chat (chat) == NULL)
883 /* No TpChat, we are disconnected */
886 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
888 icon_name = EMPATHY_IMAGE_MESSAGE;
890 else if (remote_contact && empathy_chat_is_composing (chat))
892 icon_name = EMPATHY_IMAGE_TYPING;
894 else if (empathy_chat_is_sms_channel (chat))
896 icon_name = EMPATHY_IMAGE_SMS;
898 else if (remote_contact)
900 icon_name = empathy_icon_name_for_contact (remote_contact);
904 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
907 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
908 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
910 if (icon_name != NULL)
912 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
914 gtk_widget_show (tab_image);
915 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
917 gtk_widget_show (menu_image);
921 gtk_widget_hide (tab_image);
922 gtk_widget_hide (menu_image);
925 /* Update the sending spinner */
926 nb_sending = empathy_chat_get_n_messages_sending (chat);
927 sending_spinner = g_object_get_data (G_OBJECT (chat),
928 "chat-window-tab-sending-spinner");
930 g_object_set (sending_spinner,
931 "active", nb_sending > 0,
932 "visible", nb_sending > 0,
935 /* Update tab tooltip */
936 tooltip = g_string_new (NULL);
940 id = empathy_contact_get_id (remote_contact);
941 status = empathy_contact_get_presence_message (remote_contact);
948 if (empathy_chat_is_sms_channel (chat))
949 append_markup_printf (tooltip, "%s ", _("SMS:"));
951 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
952 id, tp_account_get_display_name (account));
956 char *tmp = g_strdup_printf (
957 ngettext ("Sending %d message",
958 "Sending %d messages",
962 g_string_append (tooltip, "\n");
963 g_string_append (tooltip, tmp);
965 gtk_widget_set_tooltip_text (sending_spinner, tmp);
969 if (!EMP_STR_EMPTY (status))
970 append_markup_printf (tooltip, "\n<i>%s</i>", status);
972 if (!EMP_STR_EMPTY (subject))
973 append_markup_printf (tooltip, "\n<b>%s</b> %s",
974 _("Topic:"), subject);
976 if (remote_contact && empathy_chat_is_composing (chat))
977 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
979 if (remote_contact != NULL)
981 const gchar * const *types;
983 types = empathy_contact_get_client_types (remote_contact);
984 if (types != NULL && !tp_strdiff (types[0], "phone"))
986 /* I'm on a phone ! */
989 name = g_strdup_printf ("☎ %s", name);
994 markup = g_string_free (tooltip, FALSE);
995 widget = g_object_get_data (G_OBJECT (chat),
996 "chat-window-tab-tooltip-widget");
997 gtk_widget_set_tooltip_markup (widget, markup);
999 widget = g_object_get_data (G_OBJECT (chat),
1000 "chat-window-menu-tooltip-widget");
1001 gtk_widget_set_tooltip_markup (widget, markup);
1004 /* Update tab and menu label */
1005 if (empathy_chat_is_highlighted (chat))
1007 markup = g_markup_printf_escaped (
1008 "<span color=\"red\" weight=\"bold\">%s</span>",
1013 markup = g_markup_escape_text (name, -1);
1016 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1017 gtk_label_set_markup (GTK_LABEL (widget), markup);
1018 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1019 gtk_label_set_markup (GTK_LABEL (widget), markup);
1022 /* Update the window if it's the current chat */
1023 if (self->priv->current_chat == chat)
1024 chat_window_update (self, update_contact_menu);
1030 chat_window_update_chat_tab (EmpathyChat *chat)
1032 chat_window_update_chat_tab_full (chat, TRUE);
1036 chat_window_chat_notify_cb (EmpathyChat *chat)
1038 EmpathyChatWindow *window;
1039 EmpathyContact *old_remote_contact;
1040 EmpathyContact *remote_contact = NULL;
1042 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1043 "chat-window-remote-contact");
1044 remote_contact = empathy_chat_get_remote_contact (chat);
1046 if (old_remote_contact != remote_contact)
1048 /* The remote-contact associated with the chat changed, we need
1049 * to keep track of any change of that contact and update the
1050 * window each time. */
1052 g_signal_connect_swapped (remote_contact, "notify",
1053 G_CALLBACK (chat_window_update_chat_tab), chat);
1055 if (old_remote_contact)
1056 g_signal_handlers_disconnect_by_func (old_remote_contact,
1057 chat_window_update_chat_tab, chat);
1059 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1060 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1063 chat_window_update_chat_tab (chat);
1065 window = chat_window_find_chat (chat);
1067 chat_window_update (window, FALSE);
1071 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1072 EmpathySmiley *smiley,
1075 EmpathyChatWindow *self = user_data;
1077 GtkTextBuffer *buffer;
1080 chat = self->priv->current_chat;
1082 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1083 gtk_text_buffer_get_end_iter (buffer, &iter);
1084 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1088 chat_window_conv_activate_cb (GtkAction *action,
1089 EmpathyChatWindow *self)
1093 EmpathyContact *remote_contact = NULL;
1095 /* Favorite room menu */
1096 is_room = empathy_chat_is_room (self->priv->current_chat);
1101 gboolean found = FALSE;
1102 EmpathyChatroom *chatroom;
1104 room = empathy_chat_get_id (self->priv->current_chat);
1105 account = empathy_chat_get_account (self->priv->current_chat);
1106 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1109 if (chatroom != NULL)
1110 found = empathy_chatroom_is_favorite (chatroom);
1112 DEBUG ("This room %s favorite", found ? "is" : "is not");
1113 gtk_toggle_action_set_active (
1114 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1116 if (chatroom != NULL)
1117 found = empathy_chatroom_is_always_urgent (chatroom);
1119 gtk_toggle_action_set_active (
1120 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1123 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1124 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1126 /* Show contacts menu */
1127 g_object_get (self->priv->current_chat,
1128 "remote-contact", &remote_contact,
1129 "show-contacts", &active,
1132 if (remote_contact == NULL)
1134 gtk_toggle_action_set_active (
1135 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1138 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1139 (remote_contact == NULL));
1141 if (remote_contact != NULL)
1142 g_object_unref (remote_contact);
1146 chat_window_clear_activate_cb (GtkAction *action,
1147 EmpathyChatWindow *self)
1149 empathy_chat_clear (self->priv->current_chat);
1153 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1154 EmpathyChatWindow *self)
1160 EmpathyChatroom *chatroom;
1162 active = gtk_toggle_action_get_active (toggle_action);
1163 account = empathy_chat_get_account (self->priv->current_chat);
1164 room = empathy_chat_get_id (self->priv->current_chat);
1165 name = empathy_chat_dup_name (self->priv->current_chat);
1167 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1168 account, room, name);
1170 empathy_chatroom_set_favorite (chatroom, active);
1171 g_object_unref (chatroom);
1176 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1177 EmpathyChatWindow *self)
1183 EmpathyChatroom *chatroom;
1185 active = gtk_toggle_action_get_active (toggle_action);
1186 account = empathy_chat_get_account (self->priv->current_chat);
1187 room = empathy_chat_get_id (self->priv->current_chat);
1188 name = empathy_chat_dup_name (self->priv->current_chat);
1190 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1191 account, room, name);
1193 empathy_chatroom_set_always_urgent (chatroom, active);
1194 g_object_unref (chatroom);
1199 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1200 EmpathyChatWindow *self)
1204 active = gtk_toggle_action_get_active (toggle_action);
1206 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1210 chat_window_invite_participant_activate_cb (GtkAction *action,
1211 EmpathyChatWindow *self)
1214 EmpathyTpChat *tp_chat;
1217 g_return_if_fail (self->priv->current_chat != NULL);
1219 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1221 dialog = empathy_invite_participant_dialog_new (
1222 GTK_WINDOW (self->priv->dialog), tp_chat);
1224 gtk_widget_show (dialog);
1226 response = gtk_dialog_run (GTK_DIALOG (dialog));
1228 if (response == GTK_RESPONSE_ACCEPT)
1230 TpContact *tp_contact;
1231 EmpathyContact *contact;
1233 tp_contact = empathy_invite_participant_dialog_get_selected (
1234 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1235 if (tp_contact == NULL)
1238 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1240 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1242 g_object_unref (contact);
1246 gtk_widget_destroy (dialog);
1250 chat_window_close_activate_cb (GtkAction *action,
1251 EmpathyChatWindow *self)
1253 g_return_if_fail (self->priv->current_chat != NULL);
1255 maybe_close_chat (self, self->priv->current_chat);
1259 chat_window_edit_activate_cb (GtkAction *action,
1260 EmpathyChatWindow *self)
1262 GtkClipboard *clipboard;
1263 GtkTextBuffer *buffer;
1264 gboolean text_available;
1266 g_return_if_fail (self->priv->current_chat != NULL);
1268 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1270 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1271 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1272 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1276 buffer = gtk_text_view_get_buffer (
1277 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1279 if (gtk_text_buffer_get_has_selection (buffer))
1281 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1282 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1288 selection = empathy_theme_adium_get_has_selection (
1289 self->priv->current_chat->view);
1291 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1292 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1295 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1296 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1297 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1301 chat_window_cut_activate_cb (GtkAction *action,
1302 EmpathyChatWindow *self)
1304 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1306 empathy_chat_cut (self->priv->current_chat);
1310 chat_window_copy_activate_cb (GtkAction *action,
1311 EmpathyChatWindow *self)
1313 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1315 empathy_chat_copy (self->priv->current_chat);
1319 chat_window_paste_activate_cb (GtkAction *action,
1320 EmpathyChatWindow *self)
1322 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1324 empathy_chat_paste (self->priv->current_chat);
1328 chat_window_find_activate_cb (GtkAction *action,
1329 EmpathyChatWindow *self)
1331 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1333 empathy_chat_find (self->priv->current_chat);
1337 chat_window_tabs_next_activate_cb (GtkAction *action,
1338 EmpathyChatWindow *self)
1340 gint index_, numPages;
1341 gboolean wrap_around;
1343 g_object_get (gtk_settings_get_default (),
1344 "gtk-keynav-wrap-around", &wrap_around,
1347 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1348 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1350 if (index_ == (numPages - 1) && wrap_around)
1352 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1356 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1360 chat_window_tabs_previous_activate_cb (GtkAction *action,
1361 EmpathyChatWindow *self)
1363 gint index_, numPages;
1364 gboolean wrap_around;
1366 g_object_get (gtk_settings_get_default (),
1367 "gtk-keynav-wrap-around", &wrap_around,
1370 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1371 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1373 if (index_ <= 0 && wrap_around)
1375 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1380 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1384 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1385 EmpathyChatWindow *self)
1387 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1388 empathy_get_current_action_time ());
1392 chat_window_tabs_left_activate_cb (GtkAction *action,
1393 EmpathyChatWindow *self)
1396 gint index_, num_pages;
1398 chat = self->priv->current_chat;
1399 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1403 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1406 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1407 chat_window_menu_context_update (self, num_pages);
1411 chat_window_tabs_right_activate_cb (GtkAction *action,
1412 EmpathyChatWindow *self)
1415 gint index_, num_pages;
1417 chat = self->priv->current_chat;
1418 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1420 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1423 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1424 chat_window_menu_context_update (self, num_pages);
1427 static EmpathyChatWindow *
1428 empathy_chat_window_new (void)
1430 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1434 chat_window_detach_activate_cb (GtkAction *action,
1435 EmpathyChatWindow *self)
1437 EmpathyChatWindow *new_window;
1440 chat = self->priv->current_chat;
1441 new_window = empathy_chat_window_new ();
1443 empathy_chat_window_move_chat (self, new_window, chat);
1445 gtk_widget_show (new_window->priv->dialog);
1449 chat_window_help_contents_activate_cb (GtkAction *action,
1450 EmpathyChatWindow *self)
1452 empathy_url_show (self->priv->dialog, "help:empathy");
1456 chat_window_help_about_activate_cb (GtkAction *action,
1457 EmpathyChatWindow *self)
1459 empathy_about_dialog_new (GTK_WINDOW (self->priv->dialog));
1463 chat_window_delete_event_cb (GtkWidget *dialog,
1465 EmpathyChatWindow *self)
1467 EmpathyChat *chat = NULL;
1471 DEBUG ("Delete event received");
1473 for (l = self->priv->chats; l != NULL; l = l->next)
1475 if (chat_needs_close_confirmation (l->data))
1484 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1488 remove_all_chats (self);
1495 chat_window_composing_cb (EmpathyChat *chat,
1496 gboolean is_composing,
1497 EmpathyChatWindow *self)
1499 chat_window_update_chat_tab (chat);
1503 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1506 gtk_window_set_urgency_hint (GTK_WINDOW (self->priv->dialog), urgent);
1510 chat_window_notification_closed_cb (NotifyNotification *notify,
1511 EmpathyChatWindow *self)
1513 g_object_unref (notify);
1514 if (self->priv->notification == notify)
1515 self->priv->notification = NULL;
1519 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1520 EmpathyMessage *message,
1523 EmpathyContact *sender;
1524 const gchar *header;
1528 gboolean res, has_x_canonical_append;
1529 NotifyNotification *notification = self->priv->notification;
1531 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1534 res = g_settings_get_boolean (self->priv->gsettings_notif,
1535 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1540 sender = empathy_message_get_sender (message);
1541 header = empathy_contact_get_alias (sender);
1542 body = empathy_message_get_body (message);
1543 escaped = g_markup_escape_text (body, -1);
1545 has_x_canonical_append = empathy_notify_manager_has_capability (
1546 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1548 if (notification != NULL && !has_x_canonical_append)
1550 /* if the notification server supports x-canonical-append, it is
1551 better to not use notify_notification_update to avoid
1552 overwriting the current notification message */
1553 notify_notification_update (notification,
1554 header, escaped, NULL);
1558 /* if the notification server supports x-canonical-append,
1559 the hint will be added, so that the message from the
1560 just created notification will be automatically appended
1561 to an existing notification with the same title.
1562 In this way the previous message will not be lost: the new
1563 message will appear below it, in the same notification */
1564 const gchar *category = empathy_chat_is_room (chat)
1565 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1566 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1567 notification = notify_notification_new (header, escaped, NULL);
1569 if (self->priv->notification == NULL)
1570 self->priv->notification = notification;
1572 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1574 tp_g_signal_connect_object (notification, "closed",
1575 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1577 if (has_x_canonical_append)
1579 /* We have to set a not empty string to keep libnotify happy */
1580 notify_notification_set_hint_string (notification,
1581 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1584 notify_notification_set_hint (notification,
1585 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1588 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1589 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1593 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1594 g_object_unref (pixbuf);
1597 notify_notification_show (notification, NULL);
1603 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1607 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1609 g_object_get (self->priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1615 chat_window_new_message_cb (EmpathyChat *chat,
1616 EmpathyMessage *message,
1618 gboolean should_highlight,
1619 EmpathyChatWindow *self)
1622 gboolean needs_urgency;
1623 EmpathyContact *sender;
1625 has_focus = empathy_chat_window_has_focus (self);
1627 /* - if we're the sender, we play the sound if it's specified in the
1628 * preferences and we're not away.
1629 * - if we receive a message, we play the sound if it's specified in the
1630 * preferences and the window does not have focus on the chat receiving
1634 sender = empathy_message_get_sender (message);
1636 if (empathy_contact_is_user (sender))
1638 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self->priv->dialog),
1639 EMPATHY_SOUND_MESSAGE_OUTGOING);
1643 if (has_focus && self->priv->current_chat == chat)
1645 /* window and tab are focused so consider the message to be read */
1647 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1648 empathy_chat_messages_read (chat);
1652 /* Update the chat tab if this is the first unread message */
1653 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1655 chat_window_update_chat_tab (chat);
1658 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1659 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1660 * an unamed MUC (msn-like).
1661 * In case of a MUC, we set urgency if either:
1662 * a) the chatroom's always_urgent property is TRUE
1663 * b) the message contains our alias
1665 if (empathy_chat_is_room (chat))
1669 EmpathyChatroom *chatroom;
1671 account = empathy_chat_get_account (chat);
1672 room = empathy_chat_get_id (chat);
1674 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1677 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1678 needs_urgency = TRUE;
1680 needs_urgency = should_highlight;
1684 needs_urgency = TRUE;
1690 chat_window_set_urgency_hint (self, TRUE);
1692 /* Pending messages have already been displayed and notified in the
1693 * approver, so we don't display a notification and play a sound
1697 empathy_sound_manager_play (self->priv->sound_mgr,
1698 GTK_WIDGET (self->priv->dialog), EMPATHY_SOUND_MESSAGE_INCOMING);
1700 chat_window_show_or_update_notification (self, message, chat);
1704 /* update the number of unread messages and the window icon */
1705 chat_window_title_update (self);
1706 chat_window_icon_update (self, TRUE);
1710 chat_window_command_part (EmpathyChat *chat,
1713 EmpathyChat *chat_to_be_parted;
1714 EmpathyTpChat *tp_chat = NULL;
1716 if (strv[1] == NULL)
1718 /* No chatroom ID specified */
1719 tp_chat = empathy_chat_get_tp_chat (chat);
1722 empathy_tp_chat_leave (tp_chat, "");
1727 chat_to_be_parted = empathy_chat_window_find_chat (
1728 empathy_chat_get_account (chat), strv[1], FALSE);
1730 if (chat_to_be_parted != NULL)
1732 /* Found a chatroom matching the specified ID */
1733 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1736 empathy_tp_chat_leave (tp_chat, strv[2]);
1742 /* Going by the syntax of PART command:
1744 * /PART [<chatroom-ID>] [<reason>]
1746 * Chatroom-ID is not a must to specify a reason.
1747 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1748 * MUC then the current chatroom should be parted and srtv[1] should
1749 * be treated as part of the optional part-message. */
1750 message = g_strconcat (strv[1], " ", strv[2], NULL);
1751 tp_chat = empathy_chat_get_tp_chat (chat);
1754 empathy_tp_chat_leave (tp_chat, message);
1760 static GtkNotebook *
1761 notebook_create_window_cb (GtkNotebook *source,
1767 EmpathyChatWindow *window, *new_window;
1770 chat = EMPATHY_CHAT (page);
1771 window = chat_window_find_chat (chat);
1773 new_window = empathy_chat_window_new ();
1775 DEBUG ("Detach hook called");
1777 empathy_chat_window_move_chat (window, new_window, chat);
1779 gtk_widget_show (new_window->priv->dialog);
1780 gtk_window_move (GTK_WINDOW (new_window->priv->dialog), x, y);
1786 chat_window_page_switched_cb (GtkNotebook *notebook,
1789 EmpathyChatWindow *self)
1791 EmpathyChat *chat = EMPATHY_CHAT (child);
1793 DEBUG ("Page switched");
1795 if (self->priv->page_added)
1797 self->priv->page_added = FALSE;
1798 empathy_chat_scroll_down (chat);
1800 else if (self->priv->current_chat == chat)
1805 self->priv->current_chat = chat;
1806 empathy_chat_messages_read (chat);
1808 chat_window_update_chat_tab (chat);
1812 chat_window_page_added_cb (GtkNotebook *notebook,
1815 EmpathyChatWindow *self)
1819 /* If we just received DND to the same window, we don't want
1820 * to do anything here like removing the tab and then readding
1821 * it, so we return here and in "page-added".
1823 if (self->priv->dnd_same_window)
1825 DEBUG ("Page added (back to the same window)");
1826 self->priv->dnd_same_window = FALSE;
1830 DEBUG ("Page added");
1832 /* Get chat object */
1833 chat = EMPATHY_CHAT (child);
1835 /* Connect chat signals for this window */
1836 g_signal_connect (chat, "composing",
1837 G_CALLBACK (chat_window_composing_cb), self);
1838 g_signal_connect (chat, "new-message",
1839 G_CALLBACK (chat_window_new_message_cb), self);
1840 g_signal_connect (chat, "part-command-entered",
1841 G_CALLBACK (chat_window_command_part), NULL);
1842 g_signal_connect (chat, "notify::tp-chat",
1843 G_CALLBACK (chat_window_update_chat_tab), self);
1845 /* Set flag so we know to perform some special operations on
1846 * switch page due to the new page being added.
1848 self->priv->page_added = TRUE;
1850 /* Get list of chats up to date */
1851 self->priv->chats = g_list_append (self->priv->chats, chat);
1853 chat_window_update_chat_tab (chat);
1857 chat_window_page_removed_cb (GtkNotebook *notebook,
1860 EmpathyChatWindow *self)
1864 /* If we just received DND to the same window, we don't want
1865 * to do anything here like removing the tab and then readding
1866 * it, so we return here and in "page-added".
1868 if (self->priv->dnd_same_window)
1870 DEBUG ("Page removed (and will be readded to same window)");
1874 DEBUG ("Page removed");
1876 /* Get chat object */
1877 chat = EMPATHY_CHAT (child);
1879 /* Disconnect all signal handlers for this chat and this window */
1880 g_signal_handlers_disconnect_by_func (chat,
1881 G_CALLBACK (chat_window_composing_cb), self);
1882 g_signal_handlers_disconnect_by_func (chat,
1883 G_CALLBACK (chat_window_new_message_cb), self);
1884 g_signal_handlers_disconnect_by_func (chat,
1885 G_CALLBACK (chat_window_update_chat_tab), self);
1887 /* Keep list of chats up to date */
1888 self->priv->chats = g_list_remove (self->priv->chats, chat);
1889 empathy_chat_messages_read (chat);
1891 if (self->priv->chats == NULL)
1893 g_object_unref (self);
1897 chat_window_update (self, TRUE);
1902 chat_window_focus_in_event_cb (GtkWidget *widget,
1904 EmpathyChatWindow *self)
1906 empathy_chat_messages_read (self->priv->current_chat);
1908 chat_window_set_urgency_hint (self, FALSE);
1910 /* Update the title, since we now mark all unread messages as read. */
1911 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1917 chat_window_drag_drop (GtkWidget *widget,
1918 GdkDragContext *context,
1922 EmpathyChatWindow *self)
1926 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1927 if (target == GDK_NONE)
1928 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1930 if (target != GDK_NONE)
1932 gtk_drag_get_data (widget, context, target, time_);
1940 chat_window_drag_motion (GtkWidget *widget,
1941 GdkDragContext *context,
1945 EmpathyChatWindow *self)
1949 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1951 if (target != GDK_NONE)
1953 /* This is a file drag. Ensure the contact is online and set the
1954 drag type to COPY. Note that it's possible that the tab will
1955 be switched by GTK+ after a timeout from drag_motion without
1956 getting another drag_motion to disable the drop. You have
1957 to hold your mouse really still.
1959 EmpathyContact *contact;
1961 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
1963 /* contact is NULL for multi-user chats. We don't do
1964 * file transfers to MUCs. We also don't send files
1965 * to offline contacts or contacts that don't support
1968 if ((contact == NULL) || !empathy_contact_is_online (contact))
1970 gdk_drag_status (context, 0, time_);
1974 if (!(empathy_contact_get_capabilities (contact)
1975 & EMPATHY_CAPABILITIES_FT))
1977 gdk_drag_status (context, 0, time_);
1981 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1985 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1986 if (target != GDK_NONE)
1988 /* This is a drag of a contact from a contact list. Set to COPY.
1989 FIXME: If this drag is to a MUC window, it invites the user.
1990 Otherwise, it opens a chat. Should we use a different drag
1991 type for invites? Should we allow ASK?
1993 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2001 drag_data_received_individual_id (EmpathyChatWindow *self,
2003 GdkDragContext *context,
2006 GtkSelectionData *selection,
2011 EmpathyIndividualManager *manager = NULL;
2012 FolksIndividual *individual;
2013 EmpathyTpChat *chat;
2014 TpContact *tp_contact;
2016 EmpathyContact *contact;
2018 id = (const gchar *) gtk_selection_data_get_data (selection);
2020 DEBUG ("DND invididual %s", id);
2022 if (self->priv->current_chat == NULL)
2025 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2029 if (!empathy_tp_chat_can_add_contact (chat))
2031 DEBUG ("Can't invite contact to %s",
2032 tp_proxy_get_object_path (chat));
2036 manager = empathy_individual_manager_dup_singleton ();
2038 individual = empathy_individual_manager_lookup_member (manager, id);
2039 if (individual == NULL)
2041 DEBUG ("Failed to find individual %s", id);
2045 conn = tp_channel_get_connection ((TpChannel *) chat);
2046 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2047 if (tp_contact == NULL)
2049 DEBUG ("Can't find a TpContact on connection %s for %s",
2050 tp_proxy_get_object_path (conn), id);
2054 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2055 tp_channel_get_identifier ((TpChannel *) chat));
2057 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2058 empathy_tp_chat_add (chat, contact, NULL);
2059 g_object_unref (contact);
2062 gtk_drag_finish (context, TRUE, FALSE, time_);
2063 tp_clear_object (&manager);
2067 chat_window_drag_data_received (GtkWidget *widget,
2068 GdkDragContext *context,
2071 GtkSelectionData *selection,
2074 EmpathyChatWindow *self)
2076 if (info == DND_DRAG_TYPE_CONTACT_ID)
2078 EmpathyChat *chat = NULL;
2079 EmpathyChatWindow *old_window;
2080 TpAccount *account = NULL;
2081 EmpathyClientFactory *factory;
2084 const gchar *account_id;
2085 const gchar *contact_id;
2087 id = (const gchar*) gtk_selection_data_get_data (selection);
2089 factory = empathy_client_factory_dup ();
2091 DEBUG ("DND contact from roster with id:'%s'", id);
2093 strv = g_strsplit (id, ":", 2);
2094 if (g_strv_length (strv) == 2)
2096 account_id = strv[0];
2097 contact_id = strv[1];
2099 account = tp_simple_client_factory_ensure_account (
2100 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2102 g_object_unref (factory);
2103 if (account != NULL)
2104 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2107 if (account == NULL)
2110 gtk_drag_finish (context, FALSE, FALSE, time_);
2116 empathy_chat_with_contact_id (account, contact_id,
2117 empathy_get_current_action_time (), NULL, NULL);
2125 old_window = chat_window_find_chat (chat);
2128 if (old_window == self)
2130 gtk_drag_finish (context, TRUE, FALSE, time_);
2134 empathy_chat_window_move_chat (old_window, self, chat);
2138 empathy_chat_window_add_chat (self, chat);
2141 /* Added to take care of any outstanding chat events */
2142 empathy_chat_window_present_chat (chat,
2143 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2145 /* We should return TRUE to remove the data when doing
2146 * GDK_ACTION_MOVE, but we don't here otherwise it has
2147 * weird consequences, and we handle that internally
2148 * anyway with add_chat () and remove_chat ().
2150 gtk_drag_finish (context, TRUE, FALSE, time_);
2152 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2154 drag_data_received_individual_id (self, widget, context, x, y,
2155 selection, info, time_);
2157 else if (info == DND_DRAG_TYPE_URI_LIST)
2159 EmpathyContact *contact;
2162 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2164 /* contact is NULL when current_chat is a multi-user chat.
2165 * We don't do file transfers to MUCs, so just cancel the drag.
2167 if (contact == NULL)
2169 gtk_drag_finish (context, TRUE, FALSE, time_);
2173 data = (const gchar *) gtk_selection_data_get_data (selection);
2174 empathy_send_file_from_uri_list (contact, data);
2176 gtk_drag_finish (context, TRUE, FALSE, time_);
2178 else if (info == DND_DRAG_TYPE_TAB)
2181 EmpathyChatWindow *old_window = NULL;
2185 chat = (void *) gtk_selection_data_get_data (selection);
2186 old_window = chat_window_find_chat (*chat);
2190 self->priv->dnd_same_window = (old_window == self);
2192 DEBUG ("DND tab (within same window: %s)",
2193 self->priv->dnd_same_window ? "Yes" : "No");
2198 DEBUG ("DND from unknown source");
2199 gtk_drag_finish (context, FALSE, FALSE, time_);
2204 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2205 guint num_chats_in_manager,
2206 EmpathyChatWindow *self)
2208 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2209 num_chats_in_manager > 0);
2213 chat_window_finalize (GObject *object)
2215 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2217 DEBUG ("Finalized: %p", object);
2219 g_object_unref (self->priv->ui_manager);
2220 g_object_unref (self->priv->chatroom_manager);
2221 g_object_unref (self->priv->notify_mgr);
2222 g_object_unref (self->priv->gsettings_chat);
2223 g_object_unref (self->priv->gsettings_notif);
2224 g_object_unref (self->priv->gsettings_ui);
2225 g_object_unref (self->priv->sound_mgr);
2227 if (self->priv->notification != NULL)
2229 notify_notification_close (self->priv->notification, NULL);
2230 self->priv->notification = NULL;
2233 if (self->priv->contact_targets)
2234 gtk_target_list_unref (self->priv->contact_targets);
2236 if (self->priv->file_targets)
2237 gtk_target_list_unref (self->priv->file_targets);
2239 if (self->priv->chat_manager)
2241 g_signal_handler_disconnect (self->priv->chat_manager,
2242 self->priv->chat_manager_chats_changed_id);
2243 g_object_unref (self->priv->chat_manager);
2244 self->priv->chat_manager = NULL;
2247 chat_windows = g_list_remove (chat_windows, self);
2248 gtk_widget_destroy (self->priv->dialog);
2250 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2254 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2256 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2258 object_class->finalize = chat_window_finalize;
2260 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2264 empathy_chat_window_init (EmpathyChatWindow *self)
2267 GtkAccelGroup *accel_group;
2272 GtkWidget *chat_vbox;
2274 EmpathySmileyManager *smiley_manager;
2276 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2277 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2279 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2280 gui = empathy_builder_get_file (filename,
2281 "chat_window", &self->priv->dialog,
2282 "chat_vbox", &chat_vbox,
2283 "ui_manager", &self->priv->ui_manager,
2284 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2285 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2286 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2287 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2288 "menu_edit_cut", &self->priv->menu_edit_cut,
2289 "menu_edit_copy", &self->priv->menu_edit_copy,
2290 "menu_edit_paste", &self->priv->menu_edit_paste,
2291 "menu_edit_find", &self->priv->menu_edit_find,
2292 "menu_tabs_next", &self->priv->menu_tabs_next,
2293 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2294 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2295 "menu_tabs_left", &self->priv->menu_tabs_left,
2296 "menu_tabs_right", &self->priv->menu_tabs_right,
2297 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2301 empathy_builder_connect (gui, self,
2302 "menu_conv", "activate", chat_window_conv_activate_cb,
2303 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2304 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2305 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2306 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2307 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2308 "menu_conv_close", "activate", chat_window_close_activate_cb,
2309 "menu_edit", "activate", chat_window_edit_activate_cb,
2310 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2311 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2312 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2313 "menu_edit_find", "activate", chat_window_find_activate_cb,
2314 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2315 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2316 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2317 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2318 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2319 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2320 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2321 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2324 g_object_ref (self->priv->ui_manager);
2325 g_object_unref (gui);
2327 empathy_set_css_provider (GTK_WIDGET (self->priv->dialog));
2329 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2330 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2331 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2332 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2334 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2336 self->priv->notebook = gtk_notebook_new ();
2338 g_signal_connect (self->priv->notebook, "create-window",
2339 G_CALLBACK (notebook_create_window_cb), self);
2341 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2342 "EmpathyChatWindow");
2343 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2344 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2345 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2346 gtk_widget_show (self->priv->notebook);
2349 accel_group = gtk_accel_group_new ();
2350 gtk_window_add_accel_group (GTK_WINDOW (self->priv->dialog), accel_group);
2352 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2354 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2357 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2361 g_object_unref (accel_group);
2363 /* Set up drag target lists */
2364 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2365 G_N_ELEMENTS (drag_types_dest_contact));
2367 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2368 G_N_ELEMENTS (drag_types_dest_file));
2370 /* Set up smiley menu */
2371 smiley_manager = empathy_smiley_manager_dup_singleton ();
2372 submenu = empathy_smiley_menu_new (smiley_manager,
2373 chat_window_insert_smiley_activate_cb, self);
2375 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2376 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2377 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2378 g_object_unref (smiley_manager);
2380 /* Set up signals we can't do with ui file since we may need to
2381 * block/unblock them at some later stage.
2384 g_signal_connect (self->priv->dialog, "delete_event",
2385 G_CALLBACK (chat_window_delete_event_cb), self);
2386 g_signal_connect (self->priv->dialog, "focus_in_event",
2387 G_CALLBACK (chat_window_focus_in_event_cb), self);
2388 g_signal_connect_after (self->priv->notebook, "switch_page",
2389 G_CALLBACK (chat_window_page_switched_cb), self);
2390 g_signal_connect (self->priv->notebook, "page_added",
2391 G_CALLBACK (chat_window_page_added_cb), self);
2392 g_signal_connect (self->priv->notebook, "page_removed",
2393 G_CALLBACK (chat_window_page_removed_cb), self);
2395 /* Set up drag and drop */
2396 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2397 GTK_DEST_DEFAULT_HIGHLIGHT,
2399 G_N_ELEMENTS (drag_types_dest),
2400 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2402 /* connect_after to allow GtkNotebook's built-in tab switching */
2403 g_signal_connect_after (self->priv->notebook, "drag-motion",
2404 G_CALLBACK (chat_window_drag_motion), self);
2405 g_signal_connect (self->priv->notebook, "drag-data-received",
2406 G_CALLBACK (chat_window_drag_data_received), self);
2407 g_signal_connect (self->priv->notebook, "drag-drop",
2408 G_CALLBACK (chat_window_drag_drop), self);
2410 chat_windows = g_list_prepend (chat_windows, self);
2412 /* Set up private details */
2413 self->priv->chats = NULL;
2414 self->priv->current_chat = NULL;
2415 self->priv->notification = NULL;
2417 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2419 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2420 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2421 self->priv->chat_manager, "closed-chats-changed",
2422 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2424 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2425 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2428 /* Returns the window to open a new tab in if there is a suitable window,
2429 * otherwise, returns NULL indicating that a new window should be added.
2431 static EmpathyChatWindow *
2432 empathy_chat_window_get_default (gboolean room)
2434 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2436 gboolean separate_windows = TRUE;
2438 separate_windows = g_settings_get_boolean (gsettings,
2439 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2441 g_object_unref (gsettings);
2443 if (separate_windows)
2444 /* Always create a new window */
2447 for (l = chat_windows; l; l = l->next)
2449 EmpathyChatWindow *chat_window;
2450 guint nb_rooms, nb_private;
2452 chat_window = l->data;
2454 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2456 /* Skip the window if there aren't any rooms in it */
2457 if (room && nb_rooms == 0)
2460 /* Skip the window if there aren't any 1-1 chats in it */
2461 if (!room && nb_private == 0)
2471 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2475 GtkWidget *popup_label;
2477 GValue value = { 0, };
2479 g_return_if_fail (self != NULL);
2480 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2482 /* Reference the chat object */
2483 g_object_ref (chat);
2485 /* If this window has just been created, position it */
2486 if (self->priv->chats == NULL)
2488 const gchar *name = "chat-window";
2489 gboolean separate_windows;
2491 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2492 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2494 if (empathy_chat_is_room (chat))
2495 name = "room-window";
2497 if (separate_windows)
2501 /* Save current position of the window */
2502 gtk_window_get_position (GTK_WINDOW (self->priv->dialog), &x, &y);
2504 /* First bind to the 'generic' name. So new window for which we didn't
2505 * save a geometry yet will have the geometry of the last saved
2506 * window (bgo #601191). */
2507 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2509 /* Restore previous position of the window so the newly created window
2510 * won't be in the same position as the latest saved window and so
2511 * completely hide it. */
2512 gtk_window_move (GTK_WINDOW (self->priv->dialog), x, y);
2514 /* Then bind it to the name of the contact/room so we'll save the
2515 * geometry specific to this window */
2516 name = empathy_chat_get_id (chat);
2519 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2522 child = GTK_WIDGET (chat);
2523 label = chat_window_create_label (self, chat, TRUE);
2524 popup_label = chat_window_create_label (self, chat, FALSE);
2525 gtk_widget_show (child);
2527 g_signal_connect (chat, "notify::name",
2528 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2529 g_signal_connect (chat, "notify::subject",
2530 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2531 g_signal_connect (chat, "notify::remote-contact",
2532 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2533 g_signal_connect (chat, "notify::sms-channel",
2534 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2535 g_signal_connect (chat, "notify::n-messages-sending",
2536 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2537 g_signal_connect (chat, "notify::nb-unread-messages",
2538 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2539 chat_window_chat_notify_cb (chat);
2541 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2543 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2544 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2545 g_value_init (&value, G_TYPE_BOOLEAN);
2546 g_value_set_boolean (&value, TRUE);
2547 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2548 child, "tab-expand" , &value);
2549 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2550 child, "tab-fill" , &value);
2551 g_value_unset (&value);
2553 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2557 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2561 EmpathyContact *remote_contact;
2562 EmpathyChatManager *chat_manager;
2564 g_return_if_fail (self != NULL);
2565 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2567 g_signal_handlers_disconnect_by_func (chat,
2568 chat_window_chat_notify_cb, NULL);
2570 remote_contact = g_object_get_data (G_OBJECT (chat),
2571 "chat-window-remote-contact");
2575 g_signal_handlers_disconnect_by_func (remote_contact,
2576 chat_window_update_chat_tab, chat);
2579 chat_manager = empathy_chat_manager_dup_singleton ();
2580 empathy_chat_manager_closed_chat (chat_manager, chat);
2581 g_object_unref (chat_manager);
2583 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2585 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2587 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2589 g_object_unref (chat);
2593 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2594 EmpathyChatWindow *new_window,
2599 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2600 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2601 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2603 widget = GTK_WIDGET (chat);
2605 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2606 G_OBJECT (widget)->ref_count);
2608 /* We reference here to make sure we don't loose the widget
2609 * and the EmpathyChat object during the move.
2611 g_object_ref (chat);
2612 g_object_ref (widget);
2614 empathy_chat_window_remove_chat (old_window, chat);
2615 empathy_chat_window_add_chat (new_window, chat);
2617 g_object_unref (widget);
2618 g_object_unref (chat);
2622 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2627 g_return_if_fail (self != NULL);
2628 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2630 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2633 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2638 empathy_chat_window_find_chat (TpAccount *account,
2640 gboolean sms_channel)
2644 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2646 for (l = chat_windows; l; l = l->next)
2648 EmpathyChatWindow *window = l->data;
2651 for (ll = window->priv->chats; ll; ll = ll->next)
2657 if (account == empathy_chat_get_account (chat) &&
2658 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2659 sms_channel == empathy_chat_is_sms_channel (chat))
2668 empathy_chat_window_present_chat (EmpathyChat *chat,
2671 EmpathyChatWindow *self;
2672 guint32 x_timestamp;
2674 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2676 self = chat_window_find_chat (chat);
2678 /* If the chat has no window, create one */
2681 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2684 self = empathy_chat_window_new ();
2686 /* we want to display the newly created window even if we
2687 * don't present it */
2688 gtk_widget_show (self->priv->dialog);
2691 empathy_chat_window_add_chat (self, chat);
2694 /* Don't force the window to show itself when it wasn't
2695 * an action by the user
2697 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2700 if (x_timestamp != GDK_CURRENT_TIME)
2702 /* Don't present or switch tab if the action was earlier than the
2703 * last actions X time, accounting for overflow and the first ever
2706 if (self->priv->x_user_action_time != 0
2707 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2710 self->priv->x_user_action_time = x_timestamp;
2713 empathy_chat_window_switch_to_chat (self, chat);
2715 /* Don't use empathy_window_present_with_time () which would move the window
2716 * to our current desktop but move to the window's desktop instead. This is
2717 * more coherent with Shell's 'app is ready' notication which moves the view
2718 * to the app desktop rather than moving the app itself. */
2719 empathy_move_to_window_desktop (GTK_WINDOW (self->priv->dialog), x_timestamp);
2721 gtk_widget_grab_focus (chat->input_text_view);
2726 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2731 guint _nb_rooms = 0, _nb_private = 0;
2733 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2735 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2741 if (nb_rooms != NULL)
2742 *nb_rooms = _nb_rooms;
2743 if (nb_private != NULL)
2744 *nb_private = _nb_private;