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;
117 static GList *chat_windows = NULL;
119 static const guint tab_accel_keys[] =
121 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
122 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
127 DND_DRAG_TYPE_CONTACT_ID,
128 DND_DRAG_TYPE_INDIVIDUAL_ID,
129 DND_DRAG_TYPE_URI_LIST,
133 static const GtkTargetEntry drag_types_dest[] =
135 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
136 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
137 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
138 /* FIXME: disabled because of bug #640513
139 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
140 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
144 static const GtkTargetEntry drag_types_dest_contact[] =
146 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
147 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
150 static const GtkTargetEntry drag_types_dest_file[] =
152 /* must be first to be prioritized, in order to receive the
153 * note's file path from Tomboy instead of an URI */
154 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
155 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
158 static void chat_window_update (EmpathyChatWindow *window,
159 gboolean update_contact_menu);
161 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
164 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
167 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
168 EmpathyChatWindow *new_window,
171 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
175 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT)
178 chat_window_accel_cb (GtkAccelGroup *accelgroup,
182 EmpathyChatWindow *self)
187 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
189 if (tab_accel_keys[i] == key)
197 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
200 static EmpathyChatWindow *
201 chat_window_find_chat (EmpathyChat *chat)
205 for (l = chat_windows; l; l = l->next)
207 EmpathyChatWindow *window = l->data;
209 ll = g_list_find (window->priv->chats, chat);
218 remove_all_chats (EmpathyChatWindow *self)
222 while (self->priv->chats)
223 empathy_chat_window_remove_chat (self, self->priv->chats->data);
225 g_object_unref (self);
229 confirm_close_response_cb (GtkWidget *dialog,
231 EmpathyChatWindow *window)
235 chat = g_object_get_data (G_OBJECT (dialog), "chat");
237 gtk_widget_destroy (dialog);
239 if (response != GTK_RESPONSE_ACCEPT)
243 empathy_chat_window_remove_chat (window, chat);
245 remove_all_chats (window);
249 confirm_close (EmpathyChatWindow *self,
250 gboolean close_window,
255 gchar *primary, *secondary;
257 g_return_if_fail (n_rooms > 0);
260 g_return_if_fail (chat == NULL);
262 g_return_if_fail (chat != NULL);
264 /* If there are no chats in this window, how could we possibly have got
267 g_return_if_fail (self->priv->chats != NULL);
269 /* Treat closing a window which only has one tab exactly like closing
272 if (close_window && self->priv->chats->next == NULL)
274 close_window = FALSE;
275 chat = self->priv->chats->data;
280 primary = g_strdup (_("Close this window?"));
284 gchar *chat_name = empathy_chat_dup_name (chat);
285 secondary = g_strdup_printf (
286 _("Closing this window will leave %s. You will "
287 "not receive any further messages until you "
294 secondary = g_strdup_printf (
295 /* Note to translators: the number of chats will
296 * always be at least 2.
299 "Closing this window will leave a chat room. You will "
300 "not receive any further messages until you rejoin it.",
301 "Closing this window will leave %u chat rooms. You will "
302 "not receive any further messages until you rejoin them.",
309 gchar *chat_name = empathy_chat_dup_name (chat);
310 primary = g_strdup_printf (_("Leave %s?"), chat_name);
311 secondary = g_strdup (
312 _("You will not receive any further messages from this chat "
313 "room until you rejoin it."));
317 dialog = gtk_message_dialog_new (
318 GTK_WINDOW (self->priv->dialog),
319 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
324 gtk_window_set_title (GTK_WINDOW (dialog), "");
325 g_object_set (dialog, "secondary-text", secondary, NULL);
330 gtk_dialog_add_button (GTK_DIALOG (dialog),
331 close_window ? _("Close window") : _("Leave room"),
332 GTK_RESPONSE_ACCEPT);
333 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
334 GTK_RESPONSE_ACCEPT);
337 g_object_set_data (G_OBJECT (dialog), "chat", chat);
339 g_signal_connect (dialog, "response",
340 G_CALLBACK (confirm_close_response_cb), self);
342 gtk_window_present (GTK_WINDOW (dialog));
345 /* Returns TRUE if we should check if the user really wants to leave. If it's
346 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
347 * the user is actually in the room as opposed to having been kicked or gone
348 * offline or something), then we should check.
351 chat_needs_close_confirmation (EmpathyChat *chat)
353 return (empathy_chat_is_room (chat) &&
354 empathy_chat_get_tp_chat (chat) != NULL);
358 maybe_close_chat (EmpathyChatWindow *window,
361 g_return_if_fail (chat != NULL);
363 if (chat_needs_close_confirmation (chat))
364 confirm_close (window, FALSE, 1, chat);
366 empathy_chat_window_remove_chat (window, chat);
370 chat_window_close_clicked_cb (GtkAction *action,
373 EmpathyChatWindow *window;
375 window = chat_window_find_chat (chat);
376 maybe_close_chat (window, chat);
380 chat_tab_style_updated_cb (GtkWidget *hbox,
384 int char_width, h, w;
385 PangoContext *context;
386 const PangoFontDescription *font_desc;
387 PangoFontMetrics *metrics;
389 button = g_object_get_data (G_OBJECT (user_data),
390 "chat-window-tab-close-button");
391 context = gtk_widget_get_pango_context (hbox);
393 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
394 GTK_STATE_FLAG_NORMAL);
396 metrics = pango_context_get_metrics (context, font_desc,
397 pango_context_get_language (context));
398 char_width = pango_font_metrics_get_approximate_char_width (metrics);
399 pango_font_metrics_unref (metrics);
401 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
402 GTK_ICON_SIZE_MENU, &w, &h);
404 /* Request at least about 12 chars width plus at least space for the status
405 * image and the close button */
406 gtk_widget_set_size_request (hbox,
407 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
409 gtk_widget_set_size_request (button, w, h);
413 create_close_button (void)
415 GtkWidget *button, *image;
416 GtkStyleContext *context;
418 button = gtk_button_new ();
420 context = gtk_widget_get_style_context (button);
421 gtk_style_context_add_class (context, "empathy-tab-close-button");
423 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
424 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
426 /* We don't want focus/keynav for the button to avoid clutter, and
427 * Ctrl-W works anyway.
429 gtk_widget_set_can_focus (button, FALSE);
430 gtk_widget_set_can_default (button, FALSE);
432 image = gtk_image_new_from_icon_name ("window-close-symbolic",
434 gtk_widget_show (image);
436 gtk_container_add (GTK_CONTAINER (button), image);
442 chat_window_create_label (EmpathyChatWindow *window,
444 gboolean is_tab_label)
447 GtkWidget *name_label;
448 GtkWidget *status_image;
449 GtkWidget *event_box;
450 GtkWidget *event_box_hbox;
451 PangoAttrList *attr_list;
452 PangoAttribute *attr;
454 /* The spacing between the button and the label. */
455 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
457 event_box = gtk_event_box_new ();
458 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
460 name_label = gtk_label_new (NULL);
462 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
464 attr_list = pango_attr_list_new ();
465 attr = pango_attr_scale_new (1/1.2);
466 attr->start_index = 0;
467 attr->end_index = -1;
468 pango_attr_list_insert (attr_list, attr);
469 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
470 pango_attr_list_unref (attr_list);
472 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
473 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
474 g_object_set_data (G_OBJECT (chat),
475 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
478 status_image = gtk_image_new ();
480 /* Spacing between the icon and label. */
481 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
483 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
484 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
486 g_object_set_data (G_OBJECT (chat),
487 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
489 g_object_set_data (G_OBJECT (chat),
490 is_tab_label ? "chat-window-tab-tooltip-widget" :
491 "chat-window-menu-tooltip-widget",
494 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
495 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
499 GtkWidget *close_button;
500 GtkWidget *sending_spinner;
502 sending_spinner = gtk_spinner_new ();
504 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
506 g_object_set_data (G_OBJECT (chat),
507 "chat-window-tab-sending-spinner",
510 close_button = create_close_button ();
511 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
514 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
516 g_signal_connect (close_button,
518 G_CALLBACK (chat_window_close_clicked_cb), chat);
520 /* React to theme changes and also setup the size correctly. */
521 g_signal_connect (hbox, "style-updated",
522 G_CALLBACK (chat_tab_style_updated_cb), chat);
525 gtk_widget_show_all (hbox);
531 _submenu_notify_visible_changed_cb (GObject *object,
535 g_signal_handlers_disconnect_by_func (object,
536 _submenu_notify_visible_changed_cb, userdata);
538 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
542 chat_window_menu_context_update (EmpathyChatWindow *self,
547 gboolean wrap_around;
548 gboolean is_connected;
551 page_num = gtk_notebook_get_current_page (
552 GTK_NOTEBOOK (self->priv->notebook));
553 first_page = (page_num == 0);
554 last_page = (page_num == (num_pages - 1));
555 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
557 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
559 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
561 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
563 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
564 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
565 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
566 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
570 chat_window_conversation_menu_update (EmpathyChatWindow *self)
572 EmpathyTpChat *tp_chat;
573 TpConnection *connection;
575 gboolean sensitive = FALSE;
577 g_return_if_fail (self->priv->current_chat != NULL);
579 action = gtk_ui_manager_get_action (self->priv->ui_manager,
580 "/chats_menubar/menu_conv/menu_conv_invite_participant");
581 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
585 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
587 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
588 (tp_connection_get_status (connection, NULL) ==
589 TP_CONNECTION_STATUS_CONNECTED);
592 gtk_action_set_sensitive (action, sensitive);
596 chat_window_contact_menu_update (EmpathyChatWindow *self,
597 EmpathyChatWindow *window)
599 GtkWidget *menu, *submenu, *orig_submenu;
601 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
602 "/chats_menubar/menu_contact");
603 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
605 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
607 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
611 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
612 g_object_set_data (G_OBJECT (submenu), "window", self->priv->dialog);
614 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
615 gtk_widget_show (menu);
616 gtk_widget_set_sensitive (menu, TRUE);
620 gtk_widget_set_sensitive (menu, FALSE);
625 tp_g_signal_connect_object (orig_submenu,
627 (GCallback)_submenu_notify_visible_changed_cb, window, 0);
632 get_all_unread_messages (EmpathyChatWindow *self)
637 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
638 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
644 get_window_title_name (EmpathyChatWindow *self)
646 gchar *active_name, *ret;
648 guint current_unread_msgs;
650 nb_chats = g_list_length (self->priv->chats);
651 g_assert (nb_chats > 0);
653 active_name = empathy_chat_dup_name (self->priv->current_chat);
655 current_unread_msgs = empathy_chat_get_nb_unread_messages (
656 self->priv->current_chat);
661 if (current_unread_msgs == 0)
662 ret = g_strdup (active_name);
664 ret = g_strdup_printf (ngettext (
666 "%s (%d unread)", current_unread_msgs),
667 active_name, current_unread_msgs);
671 guint nb_others = nb_chats - 1;
672 guint all_unread_msgs;
674 all_unread_msgs = get_all_unread_messages (self);
676 if (all_unread_msgs == 0)
678 /* no unread message */
679 ret = g_strdup_printf (ngettext (
681 "%s (and %u others)", nb_others),
682 active_name, nb_others);
684 else if (all_unread_msgs == current_unread_msgs)
686 /* unread messages are in the current tab */
687 ret = g_strdup_printf (ngettext (
689 "%s (%d unread)", current_unread_msgs),
690 active_name, current_unread_msgs);
692 else if (current_unread_msgs == 0)
694 /* unread messages are in other tabs */
695 ret = g_strdup_printf (ngettext (
696 "%s (%d unread from others)",
697 "%s (%d unread from others)",
699 active_name, all_unread_msgs);
703 /* unread messages are in all the tabs */
704 ret = g_strdup_printf (ngettext (
705 "%s (%d unread from all)",
706 "%s (%d unread from all)",
708 active_name, all_unread_msgs);
712 g_free (active_name);
718 chat_window_title_update (EmpathyChatWindow *self)
722 name = get_window_title_name (self);
723 gtk_window_set_title (GTK_WINDOW (self->priv->dialog), name);
728 chat_window_icon_update (EmpathyChatWindow *self,
729 gboolean new_messages)
732 EmpathyContact *remote_contact;
733 gboolean avatar_in_icon;
736 n_chats = g_list_length (self->priv->chats);
738 /* Update window icon */
741 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog),
742 EMPATHY_IMAGE_MESSAGE);
746 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
747 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
749 if (n_chats == 1 && avatar_in_icon)
751 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
752 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
754 gtk_window_set_icon (GTK_WINDOW (self->priv->dialog), icon);
757 g_object_unref (icon);
761 gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog), NULL);
767 chat_window_close_button_update (EmpathyChatWindow *self,
771 GtkWidget *chat_close_button;
776 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
777 chat_close_button = g_object_get_data (G_OBJECT (chat),
778 "chat-window-tab-close-button");
779 gtk_widget_hide (chat_close_button);
783 for (i=0; i<num_pages; i++)
785 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
786 chat_close_button = g_object_get_data (G_OBJECT (chat),
787 "chat-window-tab-close-button");
788 gtk_widget_show (chat_close_button);
794 chat_window_update (EmpathyChatWindow *self,
795 gboolean update_contact_menu)
799 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
801 /* Update Tab menu */
802 chat_window_menu_context_update (self, num_pages);
804 chat_window_conversation_menu_update (self);
806 /* If this update is due to a focus-in event, we know the menu will be
807 the same as when we last left it, so no work to do. Besides, if we
808 swap out the menu on a focus-in, we may confuse any external global
810 if (update_contact_menu)
812 chat_window_contact_menu_update (self, self);
815 chat_window_title_update (self);
817 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
819 chat_window_close_button_update (self, num_pages);
823 append_markup_printf (GString *string,
830 va_start (args, format);
832 tmp = g_markup_vprintf_escaped (format, args);
833 g_string_append (string, tmp);
840 chat_window_update_chat_tab_full (EmpathyChat *chat,
841 gboolean update_contact_menu)
843 EmpathyChatWindow *self;
844 EmpathyContact *remote_contact;
848 const gchar *subject;
849 const gchar *status = NULL;
853 const gchar *icon_name;
854 GtkWidget *tab_image;
855 GtkWidget *menu_image;
856 GtkWidget *sending_spinner;
859 self = chat_window_find_chat (chat);
863 /* Get information */
864 name = empathy_chat_dup_name (chat);
865 account = empathy_chat_get_account (chat);
866 subject = empathy_chat_get_subject (chat);
867 remote_contact = empathy_chat_get_remote_contact (chat);
869 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
871 name, tp_proxy_get_object_path (account), subject, remote_contact);
873 /* Update tab image */
874 if (empathy_chat_get_tp_chat (chat) == NULL)
876 /* No TpChat, we are disconnected */
879 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
881 icon_name = EMPATHY_IMAGE_MESSAGE;
883 else if (remote_contact && empathy_chat_is_composing (chat))
885 icon_name = EMPATHY_IMAGE_TYPING;
887 else if (empathy_chat_is_sms_channel (chat))
889 icon_name = EMPATHY_IMAGE_SMS;
891 else if (remote_contact)
893 icon_name = empathy_icon_name_for_contact (remote_contact);
897 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
900 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
901 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
903 if (icon_name != NULL)
905 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
907 gtk_widget_show (tab_image);
908 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
910 gtk_widget_show (menu_image);
914 gtk_widget_hide (tab_image);
915 gtk_widget_hide (menu_image);
918 /* Update the sending spinner */
919 nb_sending = empathy_chat_get_n_messages_sending (chat);
920 sending_spinner = g_object_get_data (G_OBJECT (chat),
921 "chat-window-tab-sending-spinner");
923 g_object_set (sending_spinner,
924 "active", nb_sending > 0,
925 "visible", nb_sending > 0,
928 /* Update tab tooltip */
929 tooltip = g_string_new (NULL);
933 id = empathy_contact_get_id (remote_contact);
934 status = empathy_contact_get_presence_message (remote_contact);
941 if (empathy_chat_is_sms_channel (chat))
942 append_markup_printf (tooltip, "%s ", _("SMS:"));
944 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
945 id, tp_account_get_display_name (account));
949 char *tmp = g_strdup_printf (
950 ngettext ("Sending %d message",
951 "Sending %d messages",
955 g_string_append (tooltip, "\n");
956 g_string_append (tooltip, tmp);
958 gtk_widget_set_tooltip_text (sending_spinner, tmp);
962 if (!EMP_STR_EMPTY (status))
963 append_markup_printf (tooltip, "\n<i>%s</i>", status);
965 if (!EMP_STR_EMPTY (subject))
966 append_markup_printf (tooltip, "\n<b>%s</b> %s",
967 _("Topic:"), subject);
969 if (remote_contact && empathy_chat_is_composing (chat))
970 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
972 if (remote_contact != NULL)
974 const gchar * const *types;
976 types = empathy_contact_get_client_types (remote_contact);
977 if (types != NULL && !tp_strdiff (types[0], "phone"))
979 /* I'm on a phone ! */
982 name = g_strdup_printf ("☎ %s", name);
987 markup = g_string_free (tooltip, FALSE);
988 widget = g_object_get_data (G_OBJECT (chat),
989 "chat-window-tab-tooltip-widget");
990 gtk_widget_set_tooltip_markup (widget, markup);
992 widget = g_object_get_data (G_OBJECT (chat),
993 "chat-window-menu-tooltip-widget");
994 gtk_widget_set_tooltip_markup (widget, markup);
997 /* Update tab and menu label */
998 if (empathy_chat_is_highlighted (chat))
1000 markup = g_markup_printf_escaped (
1001 "<span color=\"red\" weight=\"bold\">%s</span>",
1006 markup = g_markup_escape_text (name, -1);
1009 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1010 gtk_label_set_markup (GTK_LABEL (widget), markup);
1011 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1012 gtk_label_set_markup (GTK_LABEL (widget), markup);
1015 /* Update the window if it's the current chat */
1016 if (self->priv->current_chat == chat)
1017 chat_window_update (self, update_contact_menu);
1023 chat_window_update_chat_tab (EmpathyChat *chat)
1025 chat_window_update_chat_tab_full (chat, TRUE);
1029 chat_window_chat_notify_cb (EmpathyChat *chat)
1031 EmpathyChatWindow *window;
1032 EmpathyContact *old_remote_contact;
1033 EmpathyContact *remote_contact = NULL;
1035 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1036 "chat-window-remote-contact");
1037 remote_contact = empathy_chat_get_remote_contact (chat);
1039 if (old_remote_contact != remote_contact)
1041 /* The remote-contact associated with the chat changed, we need
1042 * to keep track of any change of that contact and update the
1043 * window each time. */
1045 g_signal_connect_swapped (remote_contact, "notify",
1046 G_CALLBACK (chat_window_update_chat_tab), chat);
1048 if (old_remote_contact)
1049 g_signal_handlers_disconnect_by_func (old_remote_contact,
1050 chat_window_update_chat_tab, chat);
1052 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1053 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1056 chat_window_update_chat_tab (chat);
1058 window = chat_window_find_chat (chat);
1060 chat_window_update (window, FALSE);
1064 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1065 EmpathySmiley *smiley,
1068 EmpathyChatWindow *self = user_data;
1070 GtkTextBuffer *buffer;
1073 chat = self->priv->current_chat;
1075 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1076 gtk_text_buffer_get_end_iter (buffer, &iter);
1077 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1081 chat_window_conv_activate_cb (GtkAction *action,
1082 EmpathyChatWindow *self)
1086 EmpathyContact *remote_contact = NULL;
1088 /* Favorite room menu */
1089 is_room = empathy_chat_is_room (self->priv->current_chat);
1094 gboolean found = FALSE;
1095 EmpathyChatroom *chatroom;
1097 room = empathy_chat_get_id (self->priv->current_chat);
1098 account = empathy_chat_get_account (self->priv->current_chat);
1099 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1102 if (chatroom != NULL)
1103 found = empathy_chatroom_is_favorite (chatroom);
1105 DEBUG ("This room %s favorite", found ? "is" : "is not");
1106 gtk_toggle_action_set_active (
1107 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1109 if (chatroom != NULL)
1110 found = empathy_chatroom_is_always_urgent (chatroom);
1112 gtk_toggle_action_set_active (
1113 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1116 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1117 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1119 /* Show contacts menu */
1120 g_object_get (self->priv->current_chat,
1121 "remote-contact", &remote_contact,
1122 "show-contacts", &active,
1125 if (remote_contact == NULL)
1127 gtk_toggle_action_set_active (
1128 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1131 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1132 (remote_contact == NULL));
1134 if (remote_contact != NULL)
1135 g_object_unref (remote_contact);
1139 chat_window_clear_activate_cb (GtkAction *action,
1140 EmpathyChatWindow *self)
1142 empathy_chat_clear (self->priv->current_chat);
1146 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1147 EmpathyChatWindow *self)
1153 EmpathyChatroom *chatroom;
1155 active = gtk_toggle_action_get_active (toggle_action);
1156 account = empathy_chat_get_account (self->priv->current_chat);
1157 room = empathy_chat_get_id (self->priv->current_chat);
1158 name = empathy_chat_dup_name (self->priv->current_chat);
1160 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1161 account, room, name);
1163 empathy_chatroom_set_favorite (chatroom, active);
1164 g_object_unref (chatroom);
1169 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1170 EmpathyChatWindow *self)
1176 EmpathyChatroom *chatroom;
1178 active = gtk_toggle_action_get_active (toggle_action);
1179 account = empathy_chat_get_account (self->priv->current_chat);
1180 room = empathy_chat_get_id (self->priv->current_chat);
1181 name = empathy_chat_dup_name (self->priv->current_chat);
1183 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1184 account, room, name);
1186 empathy_chatroom_set_always_urgent (chatroom, active);
1187 g_object_unref (chatroom);
1192 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1193 EmpathyChatWindow *self)
1197 active = gtk_toggle_action_get_active (toggle_action);
1199 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1203 chat_window_invite_participant_activate_cb (GtkAction *action,
1204 EmpathyChatWindow *self)
1207 EmpathyTpChat *tp_chat;
1210 g_return_if_fail (self->priv->current_chat != NULL);
1212 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1214 dialog = empathy_invite_participant_dialog_new (
1215 GTK_WINDOW (self->priv->dialog), tp_chat);
1217 gtk_widget_show (dialog);
1219 response = gtk_dialog_run (GTK_DIALOG (dialog));
1221 if (response == GTK_RESPONSE_ACCEPT)
1223 TpContact *tp_contact;
1224 EmpathyContact *contact;
1226 tp_contact = empathy_invite_participant_dialog_get_selected (
1227 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1228 if (tp_contact == NULL)
1231 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1233 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1235 g_object_unref (contact);
1239 gtk_widget_destroy (dialog);
1243 chat_window_close_activate_cb (GtkAction *action,
1244 EmpathyChatWindow *self)
1246 g_return_if_fail (self->priv->current_chat != NULL);
1248 maybe_close_chat (self, self->priv->current_chat);
1252 chat_window_edit_activate_cb (GtkAction *action,
1253 EmpathyChatWindow *self)
1255 GtkClipboard *clipboard;
1256 GtkTextBuffer *buffer;
1257 gboolean text_available;
1259 g_return_if_fail (self->priv->current_chat != NULL);
1261 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1263 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1264 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1265 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1269 buffer = gtk_text_view_get_buffer (
1270 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1272 if (gtk_text_buffer_get_has_selection (buffer))
1274 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1275 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1281 selection = empathy_theme_adium_get_has_selection (
1282 self->priv->current_chat->view);
1284 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1285 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1288 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1289 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1290 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1294 chat_window_cut_activate_cb (GtkAction *action,
1295 EmpathyChatWindow *self)
1297 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1299 empathy_chat_cut (self->priv->current_chat);
1303 chat_window_copy_activate_cb (GtkAction *action,
1304 EmpathyChatWindow *self)
1306 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1308 empathy_chat_copy (self->priv->current_chat);
1312 chat_window_paste_activate_cb (GtkAction *action,
1313 EmpathyChatWindow *self)
1315 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1317 empathy_chat_paste (self->priv->current_chat);
1321 chat_window_find_activate_cb (GtkAction *action,
1322 EmpathyChatWindow *self)
1324 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1326 empathy_chat_find (self->priv->current_chat);
1330 chat_window_tabs_next_activate_cb (GtkAction *action,
1331 EmpathyChatWindow *self)
1333 gint index_, numPages;
1334 gboolean wrap_around;
1336 g_object_get (gtk_settings_get_default (),
1337 "gtk-keynav-wrap-around", &wrap_around,
1340 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1341 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1343 if (index_ == (numPages - 1) && wrap_around)
1345 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1349 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1353 chat_window_tabs_previous_activate_cb (GtkAction *action,
1354 EmpathyChatWindow *self)
1356 gint index_, numPages;
1357 gboolean wrap_around;
1359 g_object_get (gtk_settings_get_default (),
1360 "gtk-keynav-wrap-around", &wrap_around,
1363 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1364 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1366 if (index_ <= 0 && wrap_around)
1368 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1373 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1377 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1378 EmpathyChatWindow *self)
1380 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1381 empathy_get_current_action_time ());
1385 chat_window_tabs_left_activate_cb (GtkAction *action,
1386 EmpathyChatWindow *self)
1389 gint index_, num_pages;
1391 chat = self->priv->current_chat;
1392 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1396 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1399 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1400 chat_window_menu_context_update (self, num_pages);
1404 chat_window_tabs_right_activate_cb (GtkAction *action,
1405 EmpathyChatWindow *self)
1408 gint index_, num_pages;
1410 chat = self->priv->current_chat;
1411 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1413 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1416 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1417 chat_window_menu_context_update (self, num_pages);
1420 static EmpathyChatWindow *
1421 empathy_chat_window_new (void)
1423 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1427 chat_window_detach_activate_cb (GtkAction *action,
1428 EmpathyChatWindow *self)
1430 EmpathyChatWindow *new_window;
1433 chat = self->priv->current_chat;
1434 new_window = empathy_chat_window_new ();
1436 empathy_chat_window_move_chat (self, new_window, chat);
1438 gtk_widget_show (new_window->priv->dialog);
1442 chat_window_help_contents_activate_cb (GtkAction *action,
1443 EmpathyChatWindow *self)
1445 empathy_url_show (self->priv->dialog, "help:empathy");
1449 chat_window_help_about_activate_cb (GtkAction *action,
1450 EmpathyChatWindow *self)
1452 empathy_about_dialog_new (GTK_WINDOW (self->priv->dialog));
1456 chat_window_delete_event_cb (GtkWidget *dialog,
1458 EmpathyChatWindow *self)
1460 EmpathyChat *chat = NULL;
1464 DEBUG ("Delete event received");
1466 for (l = self->priv->chats; l != NULL; l = l->next)
1468 if (chat_needs_close_confirmation (l->data))
1477 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1481 remove_all_chats (self);
1488 chat_window_composing_cb (EmpathyChat *chat,
1489 gboolean is_composing,
1490 EmpathyChatWindow *self)
1492 chat_window_update_chat_tab (chat);
1496 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1499 gtk_window_set_urgency_hint (GTK_WINDOW (self->priv->dialog), urgent);
1503 chat_window_notification_closed_cb (NotifyNotification *notify,
1504 EmpathyChatWindow *self)
1506 g_object_unref (notify);
1507 if (self->priv->notification == notify)
1508 self->priv->notification = NULL;
1512 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1513 EmpathyMessage *message,
1516 EmpathyContact *sender;
1517 const gchar *header;
1521 gboolean res, has_x_canonical_append;
1522 NotifyNotification *notification = self->priv->notification;
1524 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1527 res = g_settings_get_boolean (self->priv->gsettings_notif,
1528 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1533 sender = empathy_message_get_sender (message);
1534 header = empathy_contact_get_alias (sender);
1535 body = empathy_message_get_body (message);
1536 escaped = g_markup_escape_text (body, -1);
1538 has_x_canonical_append = empathy_notify_manager_has_capability (
1539 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1541 if (notification != NULL && !has_x_canonical_append)
1543 /* if the notification server supports x-canonical-append, it is
1544 better to not use notify_notification_update to avoid
1545 overwriting the current notification message */
1546 notify_notification_update (notification,
1547 header, escaped, NULL);
1551 /* if the notification server supports x-canonical-append,
1552 the hint will be added, so that the message from the
1553 just created notification will be automatically appended
1554 to an existing notification with the same title.
1555 In this way the previous message will not be lost: the new
1556 message will appear below it, in the same notification */
1557 const gchar *category = empathy_chat_is_room (chat)
1558 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1559 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1560 notification = notify_notification_new (header, escaped, NULL);
1562 if (self->priv->notification == NULL)
1563 self->priv->notification = notification;
1565 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1567 tp_g_signal_connect_object (notification, "closed",
1568 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1570 if (has_x_canonical_append)
1572 /* We have to set a not empty string to keep libnotify happy */
1573 notify_notification_set_hint_string (notification,
1574 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1577 notify_notification_set_hint (notification,
1578 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1581 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1582 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1586 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1587 g_object_unref (pixbuf);
1590 notify_notification_show (notification, NULL);
1596 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1600 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1602 g_object_get (self->priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1608 chat_window_new_message_cb (EmpathyChat *chat,
1609 EmpathyMessage *message,
1611 gboolean should_highlight,
1612 EmpathyChatWindow *self)
1615 gboolean needs_urgency;
1616 EmpathyContact *sender;
1618 has_focus = empathy_chat_window_has_focus (self);
1620 /* - if we're the sender, we play the sound if it's specified in the
1621 * preferences and we're not away.
1622 * - if we receive a message, we play the sound if it's specified in the
1623 * preferences and the window does not have focus on the chat receiving
1627 sender = empathy_message_get_sender (message);
1629 if (empathy_contact_is_user (sender))
1631 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self->priv->dialog),
1632 EMPATHY_SOUND_MESSAGE_OUTGOING);
1636 if (has_focus && self->priv->current_chat == chat)
1638 /* window and tab are focused so consider the message to be read */
1640 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1641 empathy_chat_messages_read (chat);
1645 /* Update the chat tab if this is the first unread message */
1646 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1648 chat_window_update_chat_tab (chat);
1651 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1652 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1653 * an unamed MUC (msn-like).
1654 * In case of a MUC, we set urgency if either:
1655 * a) the chatroom's always_urgent property is TRUE
1656 * b) the message contains our alias
1658 if (empathy_chat_is_room (chat))
1662 EmpathyChatroom *chatroom;
1664 account = empathy_chat_get_account (chat);
1665 room = empathy_chat_get_id (chat);
1667 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1670 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1671 needs_urgency = TRUE;
1673 needs_urgency = should_highlight;
1677 needs_urgency = TRUE;
1683 chat_window_set_urgency_hint (self, TRUE);
1685 /* Pending messages have already been displayed and notified in the
1686 * approver, so we don't display a notification and play a sound
1690 empathy_sound_manager_play (self->priv->sound_mgr,
1691 GTK_WIDGET (self->priv->dialog), EMPATHY_SOUND_MESSAGE_INCOMING);
1693 chat_window_show_or_update_notification (self, message, chat);
1697 /* update the number of unread messages and the window icon */
1698 chat_window_title_update (self);
1699 chat_window_icon_update (self, TRUE);
1703 chat_window_command_part (EmpathyChat *chat,
1706 EmpathyChat *chat_to_be_parted;
1707 EmpathyTpChat *tp_chat = NULL;
1709 if (strv[1] == NULL)
1711 /* No chatroom ID specified */
1712 tp_chat = empathy_chat_get_tp_chat (chat);
1715 empathy_tp_chat_leave (tp_chat, "");
1720 chat_to_be_parted = empathy_chat_window_find_chat (
1721 empathy_chat_get_account (chat), strv[1], FALSE);
1723 if (chat_to_be_parted != NULL)
1725 /* Found a chatroom matching the specified ID */
1726 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1729 empathy_tp_chat_leave (tp_chat, strv[2]);
1735 /* Going by the syntax of PART command:
1737 * /PART [<chatroom-ID>] [<reason>]
1739 * Chatroom-ID is not a must to specify a reason.
1740 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1741 * MUC then the current chatroom should be parted and srtv[1] should
1742 * be treated as part of the optional part-message. */
1743 message = g_strconcat (strv[1], " ", strv[2], NULL);
1744 tp_chat = empathy_chat_get_tp_chat (chat);
1747 empathy_tp_chat_leave (tp_chat, message);
1753 static GtkNotebook *
1754 notebook_create_window_cb (GtkNotebook *source,
1760 EmpathyChatWindow *window, *new_window;
1763 chat = EMPATHY_CHAT (page);
1764 window = chat_window_find_chat (chat);
1766 new_window = empathy_chat_window_new ();
1768 DEBUG ("Detach hook called");
1770 empathy_chat_window_move_chat (window, new_window, chat);
1772 gtk_widget_show (new_window->priv->dialog);
1773 gtk_window_move (GTK_WINDOW (new_window->priv->dialog), x, y);
1779 chat_window_page_switched_cb (GtkNotebook *notebook,
1782 EmpathyChatWindow *self)
1784 EmpathyChat *chat = EMPATHY_CHAT (child);
1786 DEBUG ("Page switched");
1788 if (self->priv->page_added)
1790 self->priv->page_added = FALSE;
1791 empathy_chat_scroll_down (chat);
1793 else if (self->priv->current_chat == chat)
1798 self->priv->current_chat = chat;
1799 empathy_chat_messages_read (chat);
1801 chat_window_update_chat_tab (chat);
1805 chat_window_page_added_cb (GtkNotebook *notebook,
1808 EmpathyChatWindow *self)
1812 /* If we just received DND to the same window, we don't want
1813 * to do anything here like removing the tab and then readding
1814 * it, so we return here and in "page-added".
1816 if (self->priv->dnd_same_window)
1818 DEBUG ("Page added (back to the same window)");
1819 self->priv->dnd_same_window = FALSE;
1823 DEBUG ("Page added");
1825 /* Get chat object */
1826 chat = EMPATHY_CHAT (child);
1828 /* Connect chat signals for this window */
1829 g_signal_connect (chat, "composing",
1830 G_CALLBACK (chat_window_composing_cb), self);
1831 g_signal_connect (chat, "new-message",
1832 G_CALLBACK (chat_window_new_message_cb), self);
1833 g_signal_connect (chat, "part-command-entered",
1834 G_CALLBACK (chat_window_command_part), NULL);
1835 g_signal_connect (chat, "notify::tp-chat",
1836 G_CALLBACK (chat_window_update_chat_tab), self);
1838 /* Set flag so we know to perform some special operations on
1839 * switch page due to the new page being added.
1841 self->priv->page_added = TRUE;
1843 /* Get list of chats up to date */
1844 self->priv->chats = g_list_append (self->priv->chats, chat);
1846 chat_window_update_chat_tab (chat);
1850 chat_window_page_removed_cb (GtkNotebook *notebook,
1853 EmpathyChatWindow *self)
1857 /* If we just received DND to the same window, we don't want
1858 * to do anything here like removing the tab and then readding
1859 * it, so we return here and in "page-added".
1861 if (self->priv->dnd_same_window)
1863 DEBUG ("Page removed (and will be readded to same window)");
1867 DEBUG ("Page removed");
1869 /* Get chat object */
1870 chat = EMPATHY_CHAT (child);
1872 /* Disconnect all signal handlers for this chat and this window */
1873 g_signal_handlers_disconnect_by_func (chat,
1874 G_CALLBACK (chat_window_composing_cb), self);
1875 g_signal_handlers_disconnect_by_func (chat,
1876 G_CALLBACK (chat_window_new_message_cb), self);
1877 g_signal_handlers_disconnect_by_func (chat,
1878 G_CALLBACK (chat_window_update_chat_tab), self);
1880 /* Keep list of chats up to date */
1881 self->priv->chats = g_list_remove (self->priv->chats, chat);
1882 empathy_chat_messages_read (chat);
1884 if (self->priv->chats == NULL)
1886 g_object_unref (self);
1890 chat_window_update (self, TRUE);
1895 chat_window_focus_in_event_cb (GtkWidget *widget,
1897 EmpathyChatWindow *self)
1899 empathy_chat_messages_read (self->priv->current_chat);
1901 chat_window_set_urgency_hint (self, FALSE);
1903 /* Update the title, since we now mark all unread messages as read. */
1904 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1910 chat_window_drag_drop (GtkWidget *widget,
1911 GdkDragContext *context,
1915 EmpathyChatWindow *self)
1919 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1920 if (target == GDK_NONE)
1921 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1923 if (target != GDK_NONE)
1925 gtk_drag_get_data (widget, context, target, time_);
1933 chat_window_drag_motion (GtkWidget *widget,
1934 GdkDragContext *context,
1938 EmpathyChatWindow *self)
1942 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1944 if (target != GDK_NONE)
1946 /* This is a file drag. Ensure the contact is online and set the
1947 drag type to COPY. Note that it's possible that the tab will
1948 be switched by GTK+ after a timeout from drag_motion without
1949 getting another drag_motion to disable the drop. You have
1950 to hold your mouse really still.
1952 EmpathyContact *contact;
1954 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
1956 /* contact is NULL for multi-user chats. We don't do
1957 * file transfers to MUCs. We also don't send files
1958 * to offline contacts or contacts that don't support
1961 if ((contact == NULL) || !empathy_contact_is_online (contact))
1963 gdk_drag_status (context, 0, time_);
1967 if (!(empathy_contact_get_capabilities (contact)
1968 & EMPATHY_CAPABILITIES_FT))
1970 gdk_drag_status (context, 0, time_);
1974 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1978 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1979 if (target != GDK_NONE)
1981 /* This is a drag of a contact from a contact list. Set to COPY.
1982 FIXME: If this drag is to a MUC window, it invites the user.
1983 Otherwise, it opens a chat. Should we use a different drag
1984 type for invites? Should we allow ASK?
1986 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1994 drag_data_received_individual_id (EmpathyChatWindow *self,
1996 GdkDragContext *context,
1999 GtkSelectionData *selection,
2004 EmpathyIndividualManager *manager = NULL;
2005 FolksIndividual *individual;
2006 EmpathyTpChat *chat;
2007 TpContact *tp_contact;
2009 EmpathyContact *contact;
2011 id = (const gchar *) gtk_selection_data_get_data (selection);
2013 DEBUG ("DND invididual %s", id);
2015 if (self->priv->current_chat == NULL)
2018 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2022 if (!empathy_tp_chat_can_add_contact (chat))
2024 DEBUG ("Can't invite contact to %s",
2025 tp_proxy_get_object_path (chat));
2029 manager = empathy_individual_manager_dup_singleton ();
2031 individual = empathy_individual_manager_lookup_member (manager, id);
2032 if (individual == NULL)
2034 DEBUG ("Failed to find individual %s", id);
2038 conn = tp_channel_get_connection ((TpChannel *) chat);
2039 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2040 if (tp_contact == NULL)
2042 DEBUG ("Can't find a TpContact on connection %s for %s",
2043 tp_proxy_get_object_path (conn), id);
2047 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2048 tp_channel_get_identifier ((TpChannel *) chat));
2050 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2051 empathy_tp_chat_add (chat, contact, NULL);
2052 g_object_unref (contact);
2055 gtk_drag_finish (context, TRUE, FALSE, time_);
2056 tp_clear_object (&manager);
2060 chat_window_drag_data_received (GtkWidget *widget,
2061 GdkDragContext *context,
2064 GtkSelectionData *selection,
2067 EmpathyChatWindow *self)
2069 if (info == DND_DRAG_TYPE_CONTACT_ID)
2071 EmpathyChat *chat = NULL;
2072 EmpathyChatWindow *old_window;
2073 TpAccount *account = NULL;
2074 EmpathyClientFactory *factory;
2077 const gchar *account_id;
2078 const gchar *contact_id;
2080 id = (const gchar*) gtk_selection_data_get_data (selection);
2082 factory = empathy_client_factory_dup ();
2084 DEBUG ("DND contact from roster with id:'%s'", id);
2086 strv = g_strsplit (id, ":", 2);
2087 if (g_strv_length (strv) == 2)
2089 account_id = strv[0];
2090 contact_id = strv[1];
2092 account = tp_simple_client_factory_ensure_account (
2093 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2095 g_object_unref (factory);
2096 if (account != NULL)
2097 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2100 if (account == NULL)
2103 gtk_drag_finish (context, FALSE, FALSE, time_);
2109 empathy_chat_with_contact_id (account, contact_id,
2110 empathy_get_current_action_time (), NULL, NULL);
2118 old_window = chat_window_find_chat (chat);
2121 if (old_window == self)
2123 gtk_drag_finish (context, TRUE, FALSE, time_);
2127 empathy_chat_window_move_chat (old_window, self, chat);
2131 empathy_chat_window_add_chat (self, chat);
2134 /* Added to take care of any outstanding chat events */
2135 empathy_chat_window_present_chat (chat,
2136 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2138 /* We should return TRUE to remove the data when doing
2139 * GDK_ACTION_MOVE, but we don't here otherwise it has
2140 * weird consequences, and we handle that internally
2141 * anyway with add_chat () and remove_chat ().
2143 gtk_drag_finish (context, TRUE, FALSE, time_);
2145 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2147 drag_data_received_individual_id (self, widget, context, x, y,
2148 selection, info, time_);
2150 else if (info == DND_DRAG_TYPE_URI_LIST)
2152 EmpathyContact *contact;
2155 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2157 /* contact is NULL when current_chat is a multi-user chat.
2158 * We don't do file transfers to MUCs, so just cancel the drag.
2160 if (contact == NULL)
2162 gtk_drag_finish (context, TRUE, FALSE, time_);
2166 data = (const gchar *) gtk_selection_data_get_data (selection);
2167 empathy_send_file_from_uri_list (contact, data);
2169 gtk_drag_finish (context, TRUE, FALSE, time_);
2171 else if (info == DND_DRAG_TYPE_TAB)
2174 EmpathyChatWindow *old_window = NULL;
2178 chat = (void *) gtk_selection_data_get_data (selection);
2179 old_window = chat_window_find_chat (*chat);
2183 self->priv->dnd_same_window = (old_window == self);
2185 DEBUG ("DND tab (within same window: %s)",
2186 self->priv->dnd_same_window ? "Yes" : "No");
2191 DEBUG ("DND from unknown source");
2192 gtk_drag_finish (context, FALSE, FALSE, time_);
2197 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2198 guint num_chats_in_manager,
2199 EmpathyChatWindow *self)
2201 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2202 num_chats_in_manager > 0);
2206 chat_window_finalize (GObject *object)
2208 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2210 DEBUG ("Finalized: %p", object);
2212 g_object_unref (self->priv->ui_manager);
2213 g_object_unref (self->priv->chatroom_manager);
2214 g_object_unref (self->priv->notify_mgr);
2215 g_object_unref (self->priv->gsettings_chat);
2216 g_object_unref (self->priv->gsettings_notif);
2217 g_object_unref (self->priv->gsettings_ui);
2218 g_object_unref (self->priv->sound_mgr);
2220 if (self->priv->notification != NULL)
2222 notify_notification_close (self->priv->notification, NULL);
2223 self->priv->notification = NULL;
2226 if (self->priv->contact_targets)
2227 gtk_target_list_unref (self->priv->contact_targets);
2229 if (self->priv->file_targets)
2230 gtk_target_list_unref (self->priv->file_targets);
2232 if (self->priv->chat_manager)
2234 g_signal_handler_disconnect (self->priv->chat_manager,
2235 self->priv->chat_manager_chats_changed_id);
2236 g_object_unref (self->priv->chat_manager);
2237 self->priv->chat_manager = NULL;
2240 chat_windows = g_list_remove (chat_windows, self);
2241 gtk_widget_destroy (self->priv->dialog);
2243 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2247 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2249 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2251 object_class->finalize = chat_window_finalize;
2253 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2257 empathy_chat_window_init (EmpathyChatWindow *self)
2260 GtkAccelGroup *accel_group;
2265 GtkWidget *chat_vbox;
2267 EmpathySmileyManager *smiley_manager;
2269 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2270 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2272 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2273 gui = empathy_builder_get_file (filename,
2274 "chat_window", &self->priv->dialog,
2275 "chat_vbox", &chat_vbox,
2276 "ui_manager", &self->priv->ui_manager,
2277 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2278 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2279 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2280 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2281 "menu_edit_cut", &self->priv->menu_edit_cut,
2282 "menu_edit_copy", &self->priv->menu_edit_copy,
2283 "menu_edit_paste", &self->priv->menu_edit_paste,
2284 "menu_edit_find", &self->priv->menu_edit_find,
2285 "menu_tabs_next", &self->priv->menu_tabs_next,
2286 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2287 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2288 "menu_tabs_left", &self->priv->menu_tabs_left,
2289 "menu_tabs_right", &self->priv->menu_tabs_right,
2290 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2294 empathy_builder_connect (gui, self,
2295 "menu_conv", "activate", chat_window_conv_activate_cb,
2296 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2297 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2298 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2299 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2300 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2301 "menu_conv_close", "activate", chat_window_close_activate_cb,
2302 "menu_edit", "activate", chat_window_edit_activate_cb,
2303 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2304 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2305 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2306 "menu_edit_find", "activate", chat_window_find_activate_cb,
2307 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2308 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2309 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2310 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2311 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2312 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2313 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2314 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2317 g_object_ref (self->priv->ui_manager);
2318 g_object_unref (gui);
2320 empathy_set_css_provider (GTK_WIDGET (self->priv->dialog));
2322 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2323 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2324 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2325 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2327 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2329 self->priv->notebook = gtk_notebook_new ();
2331 g_signal_connect (self->priv->notebook, "create-window",
2332 G_CALLBACK (notebook_create_window_cb), self);
2334 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2335 "EmpathyChatWindow");
2336 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2337 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2338 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2339 gtk_widget_show (self->priv->notebook);
2342 accel_group = gtk_accel_group_new ();
2343 gtk_window_add_accel_group (GTK_WINDOW (self->priv->dialog), accel_group);
2345 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2347 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2350 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2354 g_object_unref (accel_group);
2356 /* Set up drag target lists */
2357 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2358 G_N_ELEMENTS (drag_types_dest_contact));
2360 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2361 G_N_ELEMENTS (drag_types_dest_file));
2363 /* Set up smiley menu */
2364 smiley_manager = empathy_smiley_manager_dup_singleton ();
2365 submenu = empathy_smiley_menu_new (smiley_manager,
2366 chat_window_insert_smiley_activate_cb, self);
2368 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2369 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2370 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2371 g_object_unref (smiley_manager);
2373 /* Set up signals we can't do with ui file since we may need to
2374 * block/unblock them at some later stage.
2377 g_signal_connect (self->priv->dialog, "delete_event",
2378 G_CALLBACK (chat_window_delete_event_cb), self);
2379 g_signal_connect (self->priv->dialog, "focus_in_event",
2380 G_CALLBACK (chat_window_focus_in_event_cb), self);
2381 g_signal_connect_after (self->priv->notebook, "switch_page",
2382 G_CALLBACK (chat_window_page_switched_cb), self);
2383 g_signal_connect (self->priv->notebook, "page_added",
2384 G_CALLBACK (chat_window_page_added_cb), self);
2385 g_signal_connect (self->priv->notebook, "page_removed",
2386 G_CALLBACK (chat_window_page_removed_cb), self);
2388 /* Set up drag and drop */
2389 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2390 GTK_DEST_DEFAULT_HIGHLIGHT,
2392 G_N_ELEMENTS (drag_types_dest),
2393 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2395 /* connect_after to allow GtkNotebook's built-in tab switching */
2396 g_signal_connect_after (self->priv->notebook, "drag-motion",
2397 G_CALLBACK (chat_window_drag_motion), self);
2398 g_signal_connect (self->priv->notebook, "drag-data-received",
2399 G_CALLBACK (chat_window_drag_data_received), self);
2400 g_signal_connect (self->priv->notebook, "drag-drop",
2401 G_CALLBACK (chat_window_drag_drop), self);
2403 chat_windows = g_list_prepend (chat_windows, self);
2405 /* Set up private details */
2406 self->priv->chats = NULL;
2407 self->priv->current_chat = NULL;
2408 self->priv->notification = NULL;
2410 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2412 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2413 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2414 self->priv->chat_manager, "closed-chats-changed",
2415 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2417 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2418 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2421 /* Returns the window to open a new tab in if there is a suitable window,
2422 * otherwise, returns NULL indicating that a new window should be added.
2424 static EmpathyChatWindow *
2425 empathy_chat_window_get_default (gboolean room)
2427 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2429 gboolean separate_windows = TRUE;
2431 separate_windows = g_settings_get_boolean (gsettings,
2432 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2434 g_object_unref (gsettings);
2436 if (separate_windows)
2437 /* Always create a new window */
2440 for (l = chat_windows; l; l = l->next)
2442 EmpathyChatWindow *chat_window;
2443 guint nb_rooms, nb_private;
2445 chat_window = l->data;
2447 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2449 /* Skip the window if there aren't any rooms in it */
2450 if (room && nb_rooms == 0)
2453 /* Skip the window if there aren't any 1-1 chats in it */
2454 if (!room && nb_private == 0)
2464 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2468 GtkWidget *popup_label;
2470 GValue value = { 0, };
2472 g_return_if_fail (self != NULL);
2473 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2475 /* Reference the chat object */
2476 g_object_ref (chat);
2478 /* If this window has just been created, position it */
2479 if (self->priv->chats == NULL)
2481 const gchar *name = "chat-window";
2482 gboolean separate_windows;
2484 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2485 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2487 if (empathy_chat_is_room (chat))
2488 name = "room-window";
2490 if (separate_windows)
2494 /* Save current position of the window */
2495 gtk_window_get_position (GTK_WINDOW (self->priv->dialog), &x, &y);
2497 /* First bind to the 'generic' name. So new window for which we didn't
2498 * save a geometry yet will have the geometry of the last saved
2499 * window (bgo #601191). */
2500 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2502 /* Restore previous position of the window so the newly created window
2503 * won't be in the same position as the latest saved window and so
2504 * completely hide it. */
2505 gtk_window_move (GTK_WINDOW (self->priv->dialog), x, y);
2507 /* Then bind it to the name of the contact/room so we'll save the
2508 * geometry specific to this window */
2509 name = empathy_chat_get_id (chat);
2512 empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2515 child = GTK_WIDGET (chat);
2516 label = chat_window_create_label (self, chat, TRUE);
2517 popup_label = chat_window_create_label (self, chat, FALSE);
2518 gtk_widget_show (child);
2520 g_signal_connect (chat, "notify::name",
2521 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2522 g_signal_connect (chat, "notify::subject",
2523 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2524 g_signal_connect (chat, "notify::remote-contact",
2525 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2526 g_signal_connect (chat, "notify::sms-channel",
2527 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2528 g_signal_connect (chat, "notify::n-messages-sending",
2529 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2530 g_signal_connect (chat, "notify::nb-unread-messages",
2531 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2532 chat_window_chat_notify_cb (chat);
2534 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2536 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2537 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2538 g_value_init (&value, G_TYPE_BOOLEAN);
2539 g_value_set_boolean (&value, TRUE);
2540 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2541 child, "tab-expand" , &value);
2542 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2543 child, "tab-fill" , &value);
2544 g_value_unset (&value);
2546 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2550 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2554 EmpathyContact *remote_contact;
2555 EmpathyChatManager *chat_manager;
2557 g_return_if_fail (self != NULL);
2558 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2560 g_signal_handlers_disconnect_by_func (chat,
2561 chat_window_chat_notify_cb, NULL);
2563 remote_contact = g_object_get_data (G_OBJECT (chat),
2564 "chat-window-remote-contact");
2568 g_signal_handlers_disconnect_by_func (remote_contact,
2569 chat_window_update_chat_tab, chat);
2572 chat_manager = empathy_chat_manager_dup_singleton ();
2573 empathy_chat_manager_closed_chat (chat_manager, chat);
2574 g_object_unref (chat_manager);
2576 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2578 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2580 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2582 g_object_unref (chat);
2586 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2587 EmpathyChatWindow *new_window,
2592 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2593 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2594 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2596 widget = GTK_WIDGET (chat);
2598 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2599 G_OBJECT (widget)->ref_count);
2601 /* We reference here to make sure we don't loose the widget
2602 * and the EmpathyChat object during the move.
2604 g_object_ref (chat);
2605 g_object_ref (widget);
2607 empathy_chat_window_remove_chat (old_window, chat);
2608 empathy_chat_window_add_chat (new_window, chat);
2610 g_object_unref (widget);
2611 g_object_unref (chat);
2615 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2620 g_return_if_fail (self != NULL);
2621 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2623 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2626 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2631 empathy_chat_window_find_chat (TpAccount *account,
2633 gboolean sms_channel)
2637 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2639 for (l = chat_windows; l; l = l->next)
2641 EmpathyChatWindow *window = l->data;
2644 for (ll = window->priv->chats; ll; ll = ll->next)
2650 if (account == empathy_chat_get_account (chat) &&
2651 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2652 sms_channel == empathy_chat_is_sms_channel (chat))
2661 empathy_chat_window_present_chat (EmpathyChat *chat,
2664 EmpathyChatWindow *self;
2665 guint32 x_timestamp;
2667 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2669 self = chat_window_find_chat (chat);
2671 /* If the chat has no window, create one */
2674 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2677 self = empathy_chat_window_new ();
2679 /* we want to display the newly created window even if we
2680 * don't present it */
2681 gtk_widget_show (self->priv->dialog);
2684 empathy_chat_window_add_chat (self, chat);
2687 /* Don't force the window to show itself when it wasn't
2688 * an action by the user
2690 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2693 if (x_timestamp != GDK_CURRENT_TIME)
2695 /* Don't present or switch tab if the action was earlier than the
2696 * last actions X time, accounting for overflow and the first ever
2699 if (self->priv->x_user_action_time != 0
2700 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2703 self->priv->x_user_action_time = x_timestamp;
2706 empathy_chat_window_switch_to_chat (self, chat);
2708 /* Don't use empathy_window_present_with_time () which would move the window
2709 * to our current desktop but move to the window's desktop instead. This is
2710 * more coherent with Shell's 'app is ready' notication which moves the view
2711 * to the app desktop rather than moving the app itself. */
2712 empathy_move_to_window_desktop (GTK_WINDOW (self->priv->dialog), x_timestamp);
2714 gtk_widget_grab_focus (chat->input_text_view);
2718 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2723 guint _nb_rooms = 0, _nb_private = 0;
2725 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2727 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2733 if (nb_rooms != NULL)
2734 *nb_rooms = _nb_rooms;
2735 if (nb_private != NULL)
2736 *nb_private = _nb_private;