hindi update
[empathy-mirror.git] / src / empathy-chat-window.c
blob97ed1b9de51c7696308f5015037b261338f31358
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2003-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Richard Hult <richard@imendio.com>
23 * Martyn Russell <martyn@imendio.com>
24 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
25 * Xavier Claessens <xclaesse@gmail.com>
26 * RĂ´mulo Fernandes Machado <romulo@castorgroup.net>
29 #include <config.h>
31 #include <string.h>
33 #include <gtk/gtk.h>
34 #include <gdk/gdkkeysyms.h>
35 #include <gdk/gdkx.h>
36 #include <glib/gi18n.h>
37 #include <libnotify/notification.h>
39 #include <telepathy-glib/telepathy-glib.h>
41 #include <libempathy/empathy-client-factory.h>
42 #include <libempathy/empathy-contact.h>
43 #include <libempathy/empathy-message.h>
44 #include <libempathy/empathy-chatroom-manager.h>
45 #include <libempathy/empathy-gsettings.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-tp-contact-factory.h>
48 #include <libempathy/empathy-contact-list.h>
49 #include <libempathy/empathy-request-util.h>
51 #include <libempathy-gtk/empathy-images.h>
52 #include <libempathy-gtk/empathy-contact-dialogs.h>
53 #include <libempathy-gtk/empathy-log-window.h>
54 #include <libempathy-gtk/empathy-geometry.h>
55 #include <libempathy-gtk/empathy-smiley-manager.h>
56 #include <libempathy-gtk/empathy-sound-manager.h>
57 #include <libempathy-gtk/empathy-ui-utils.h>
58 #include <libempathy-gtk/empathy-notify-manager.h>
60 #include "empathy-chat-manager.h"
61 #include "empathy-chat-window.h"
62 #include "empathy-about-dialog.h"
63 #include "empathy-invite-participant-dialog.h"
64 #include "gedit-close-button.h"
66 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
67 #include <libempathy/empathy-debug.h>
69 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
71 #define X_EARLIER_OR_EQL(t1, t2) \
72 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
73 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
76 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
77 typedef struct {
78 EmpathyChat *current_chat;
79 GList *chats;
80 gboolean page_added;
81 gboolean dnd_same_window;
82 EmpathyChatroomManager *chatroom_manager;
83 EmpathyNotifyManager *notify_mgr;
84 GtkWidget *dialog;
85 GtkWidget *notebook;
86 NotifyNotification *notification;
88 GtkTargetList *contact_targets;
89 GtkTargetList *file_targets;
91 EmpathyChatManager *chat_manager;
92 gulong chat_manager_chats_changed_id;
94 /* Menu items. */
95 GtkUIManager *ui_manager;
96 GtkAction *menu_conv_insert_smiley;
97 GtkAction *menu_conv_favorite;
98 GtkAction *menu_conv_always_urgent;
99 GtkAction *menu_conv_toggle_contacts;
101 GtkAction *menu_edit_cut;
102 GtkAction *menu_edit_copy;
103 GtkAction *menu_edit_paste;
104 GtkAction *menu_edit_find;
106 GtkAction *menu_tabs_next;
107 GtkAction *menu_tabs_prev;
108 GtkAction *menu_tabs_undo_close_tab;
109 GtkAction *menu_tabs_left;
110 GtkAction *menu_tabs_right;
111 GtkAction *menu_tabs_detach;
113 /* Last user action time we acted upon to show a tab */
114 guint32 x_user_action_time;
116 GSettings *gsettings_chat;
117 GSettings *gsettings_notif;
118 GSettings *gsettings_ui;
120 EmpathySoundManager *sound_mgr;
121 } EmpathyChatWindowPriv;
123 static GList *chat_windows = NULL;
125 static const guint tab_accel_keys[] = {
126 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
127 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
130 typedef enum {
131 DND_DRAG_TYPE_CONTACT_ID,
132 DND_DRAG_TYPE_URI_LIST,
133 DND_DRAG_TYPE_TAB
134 } DndDragType;
136 static const GtkTargetEntry drag_types_dest[] = {
137 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
138 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
139 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
140 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] = {
144 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
147 static const GtkTargetEntry drag_types_dest_file[] = {
148 /* must be first to be prioritized, in order to receive the
149 * note's file path from Tomboy instead of an URI */
150 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
151 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
154 static void chat_window_update (EmpathyChatWindow *window,
155 gboolean update_contact_menu);
157 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
158 EmpathyChat *chat);
160 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
161 EmpathyChat *chat);
163 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
164 EmpathyChatWindow *new_window,
165 EmpathyChat *chat);
167 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
168 guint *nb_rooms,
169 guint *nb_private);
171 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
173 static void
174 chat_window_accel_cb (GtkAccelGroup *accelgroup,
175 GObject *object,
176 guint key,
177 GdkModifierType mod,
178 EmpathyChatWindow *window)
180 EmpathyChatWindowPriv *priv;
181 gint num = -1;
182 guint i;
184 priv = GET_PRIV (window);
186 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
187 if (tab_accel_keys[i] == key) {
188 num = i;
189 break;
193 if (num != -1) {
194 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
198 static EmpathyChatWindow *
199 chat_window_find_chat (EmpathyChat *chat)
201 EmpathyChatWindowPriv *priv;
202 GList *l, *ll;
204 for (l = chat_windows; l; l = l->next) {
205 priv = GET_PRIV (l->data);
206 ll = g_list_find (priv->chats, chat);
207 if (ll) {
208 return l->data;
212 return NULL;
215 static void
216 chat_window_close_clicked_cb (GtkAction *action,
217 EmpathyChat *chat)
219 EmpathyChatWindow *window;
221 window = chat_window_find_chat (chat);
222 empathy_chat_window_remove_chat (window, chat);
225 static void
226 chat_tab_style_updated_cb (GtkWidget *hbox,
227 gpointer user_data)
229 GtkWidget *button;
230 int char_width, h, w;
231 PangoContext *context;
232 const PangoFontDescription *font_desc;
233 PangoFontMetrics *metrics;
235 button = g_object_get_data (G_OBJECT (user_data),
236 "chat-window-tab-close-button");
237 context = gtk_widget_get_pango_context (hbox);
239 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
240 GTK_STATE_FLAG_NORMAL);
242 metrics = pango_context_get_metrics (context, font_desc,
243 pango_context_get_language (context));
244 char_width = pango_font_metrics_get_approximate_char_width (metrics);
245 pango_font_metrics_unref (metrics);
247 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
248 GTK_ICON_SIZE_MENU, &w, &h);
250 /* Request at least about 12 chars width plus at least space for the status
251 * image and the close button */
252 gtk_widget_set_size_request (hbox,
253 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
255 gtk_widget_set_size_request (button, w, h);
258 static GtkWidget *
259 chat_window_create_label (EmpathyChatWindow *window,
260 EmpathyChat *chat,
261 gboolean is_tab_label)
263 GtkWidget *hbox;
264 GtkWidget *name_label;
265 GtkWidget *status_image;
266 GtkWidget *event_box;
267 GtkWidget *event_box_hbox;
268 PangoAttrList *attr_list;
269 PangoAttribute *attr;
271 /* The spacing between the button and the label. */
272 hbox = gtk_hbox_new (FALSE, 0);
274 event_box = gtk_event_box_new ();
275 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
277 name_label = gtk_label_new (NULL);
278 if (is_tab_label)
279 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
281 attr_list = pango_attr_list_new ();
282 attr = pango_attr_scale_new (1/1.2);
283 attr->start_index = 0;
284 attr->end_index = -1;
285 pango_attr_list_insert (attr_list, attr);
286 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
287 pango_attr_list_unref (attr_list);
289 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
290 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
291 g_object_set_data (G_OBJECT (chat),
292 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
293 name_label);
295 status_image = gtk_image_new ();
297 /* Spacing between the icon and label. */
298 event_box_hbox = gtk_hbox_new (FALSE, 0);
300 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
301 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
303 g_object_set_data (G_OBJECT (chat),
304 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
305 status_image);
306 g_object_set_data (G_OBJECT (chat),
307 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
308 event_box);
310 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
311 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
313 if (is_tab_label) {
314 GtkWidget *close_button;
315 GtkWidget *sending_spinner;
317 sending_spinner = gtk_spinner_new ();
319 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
320 FALSE, FALSE, 0);
321 g_object_set_data (G_OBJECT (chat),
322 "chat-window-tab-sending-spinner",
323 sending_spinner);
325 close_button = gedit_close_button_new ();
326 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
328 /* We don't want focus/keynav for the button to avoid clutter, and
329 * Ctrl-W works anyway.
331 gtk_widget_set_can_focus (close_button, FALSE);
332 gtk_widget_set_can_default (close_button, FALSE);
334 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
336 g_signal_connect (close_button,
337 "clicked",
338 G_CALLBACK (chat_window_close_clicked_cb),
339 chat);
341 /* React to theme changes and also setup the size correctly. */
342 g_signal_connect (hbox,
343 "style-updated",
344 G_CALLBACK (chat_tab_style_updated_cb),
345 chat);
348 gtk_widget_show_all (hbox);
350 return hbox;
353 static void
354 _submenu_notify_visible_changed_cb (GObject *object,
355 GParamSpec *pspec,
356 gpointer userdata)
358 g_signal_handlers_disconnect_by_func (object,
359 _submenu_notify_visible_changed_cb,
360 userdata);
361 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
364 static void
365 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
366 gint num_pages)
368 gboolean first_page;
369 gboolean last_page;
370 gboolean wrap_around;
371 gboolean is_connected;
372 gint page_num;
374 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
375 first_page = (page_num == 0);
376 last_page = (page_num == (num_pages - 1));
377 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
378 &wrap_around, NULL);
379 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
381 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
382 wrap_around));
383 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
384 wrap_around));
385 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
386 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
387 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
388 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
391 static void
392 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
393 EmpathyChatWindow *self)
395 EmpathyTpChat *tp_chat;
396 TpConnection *connection;
397 GtkAction *action;
398 gboolean sensitive = FALSE;
400 g_return_if_fail (priv->current_chat != NULL);
402 action = gtk_ui_manager_get_action (priv->ui_manager,
403 "/chats_menubar/menu_conv/menu_conv_invite_participant");
404 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
406 if (tp_chat != NULL) {
407 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
409 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
410 (tp_connection_get_status (connection, NULL) ==
411 TP_CONNECTION_STATUS_CONNECTED);
414 gtk_action_set_sensitive (action, sensitive);
417 static void
418 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
419 EmpathyChatWindow *window)
421 GtkWidget *menu, *submenu, *orig_submenu;
423 menu = gtk_ui_manager_get_widget (priv->ui_manager,
424 "/chats_menubar/menu_contact");
425 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
427 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
428 submenu = empathy_chat_get_contact_menu (priv->current_chat);
430 if (submenu != NULL) {
431 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
432 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
434 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
435 gtk_widget_show (menu);
436 gtk_widget_set_sensitive (menu, TRUE);
437 } else {
438 gtk_widget_set_sensitive (menu, FALSE);
440 } else {
441 tp_g_signal_connect_object (orig_submenu,
442 "notify::visible",
443 (GCallback)_submenu_notify_visible_changed_cb,
444 window, 0);
448 static guint
449 get_all_unread_messages (EmpathyChatWindowPriv *priv)
451 GList *l;
452 guint nb = 0;
454 for (l = priv->chats; l != NULL; l = g_list_next (l))
455 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
457 return nb;
460 static gchar *
461 get_window_title_name (EmpathyChatWindowPriv *priv)
463 gchar *active_name, *ret;
464 guint nb_chats;
465 guint current_unread_msgs;
467 nb_chats = g_list_length (priv->chats);
468 g_assert (nb_chats > 0);
470 active_name = empathy_chat_dup_name (priv->current_chat);
472 current_unread_msgs = empathy_chat_get_nb_unread_messages (
473 priv->current_chat);
475 if (nb_chats == 1) {
476 /* only one tab */
477 if (current_unread_msgs == 0)
478 ret = g_strdup (active_name);
479 else
480 ret = g_strdup_printf (ngettext (
481 "%s (%d unread)",
482 "%s (%d unread)", current_unread_msgs),
483 active_name, current_unread_msgs);
484 } else {
485 guint nb_others = nb_chats - 1;
486 guint all_unread_msgs;
488 all_unread_msgs = get_all_unread_messages (priv);
490 if (all_unread_msgs == 0) {
491 /* no unread message */
492 ret = g_strdup_printf (ngettext (
493 "%s (and %u other)",
494 "%s (and %u others)", nb_others),
495 active_name, nb_others);
498 else if (all_unread_msgs == current_unread_msgs) {
499 /* unread messages are in the current tab */
500 ret = g_strdup_printf (ngettext (
501 "%s (%d unread)",
502 "%s (%d unread)", current_unread_msgs),
503 active_name, current_unread_msgs);
506 else if (current_unread_msgs == 0) {
507 /* unread messages are in other tabs */
508 ret = g_strdup_printf (ngettext (
509 "%s (%d unread from others)",
510 "%s (%d unread from others)",
511 all_unread_msgs),
512 active_name, all_unread_msgs);
515 else {
516 /* unread messages are in all the tabs */
517 ret = g_strdup_printf (ngettext (
518 "%s (%d unread from all)",
519 "%s (%d unread from all)",
520 all_unread_msgs),
521 active_name, all_unread_msgs);
525 g_free (active_name);
527 return ret;
530 static void
531 chat_window_title_update (EmpathyChatWindowPriv *priv)
533 gchar *name;
535 name = get_window_title_name (priv);
536 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
537 g_free (name);
540 static void
541 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
543 GdkPixbuf *icon;
544 EmpathyContact *remote_contact;
545 gboolean avatar_in_icon;
546 guint n_chats;
548 n_chats = g_list_length (priv->chats);
550 /* Update window icon */
551 if (new_messages) {
552 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
553 EMPATHY_IMAGE_MESSAGE);
554 } else {
555 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
556 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
558 if (n_chats == 1 && avatar_in_icon) {
559 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
560 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
561 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
563 if (icon != NULL) {
564 g_object_unref (icon);
566 } else {
567 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
572 static void
573 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
574 gint num_pages)
576 GtkWidget *chat;
577 GtkWidget *chat_close_button;
578 gint i;
580 if (num_pages == 1) {
581 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
582 chat_close_button = g_object_get_data (G_OBJECT (chat),
583 "chat-window-tab-close-button");
584 gtk_widget_hide (chat_close_button);
585 } else {
586 for (i=0; i<num_pages; i++) {
587 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
588 chat_close_button = g_object_get_data (G_OBJECT (chat),
589 "chat-window-tab-close-button");
590 gtk_widget_show (chat_close_button);
595 static void
596 chat_window_update (EmpathyChatWindow *window,
597 gboolean update_contact_menu)
599 EmpathyChatWindowPriv *priv = GET_PRIV (window);
600 gint num_pages;
602 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
604 /* Update Tab menu */
605 chat_window_menu_context_update (priv,
606 num_pages);
608 chat_window_conversation_menu_update (priv, window);
610 /* If this update is due to a focus-in event, we know the menu will be
611 the same as when we last left it, so no work to do. Besides, if we
612 swap out the menu on a focus-in, we may confuse any external global
613 menu watching. */
614 if (update_contact_menu) {
615 chat_window_contact_menu_update (priv,
616 window);
619 chat_window_title_update (priv);
621 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
623 chat_window_close_button_update (priv,
624 num_pages);
627 static void
628 append_markup_printf (GString *string,
629 const char *format,
630 ...)
632 gchar *tmp;
633 va_list args;
635 va_start (args, format);
637 tmp = g_markup_vprintf_escaped (format, args);
638 g_string_append (string, tmp);
639 g_free (tmp);
641 va_end (args);
644 static void
645 chat_window_update_chat_tab_full (EmpathyChat *chat,
646 gboolean update_contact_menu)
648 EmpathyChatWindow *window;
649 EmpathyChatWindowPriv *priv;
650 EmpathyContact *remote_contact;
651 gchar *name;
652 const gchar *id;
653 TpAccount *account;
654 const gchar *subject;
655 const gchar *status = NULL;
656 GtkWidget *widget;
657 GString *tooltip;
658 gchar *markup;
659 const gchar *icon_name;
660 GtkWidget *tab_image;
661 GtkWidget *menu_image;
662 GtkWidget *sending_spinner;
663 guint nb_sending;
665 window = chat_window_find_chat (chat);
666 if (!window) {
667 return;
669 priv = GET_PRIV (window);
671 /* Get information */
672 name = empathy_chat_dup_name (chat);
673 account = empathy_chat_get_account (chat);
674 subject = empathy_chat_get_subject (chat);
675 remote_contact = empathy_chat_get_remote_contact (chat);
677 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
678 name, tp_proxy_get_object_path (account), subject, remote_contact);
680 /* Update tab image */
681 if (empathy_chat_get_tp_chat (chat) == NULL) {
682 /* No TpChat, we are disconnected */
683 icon_name = NULL;
685 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
686 icon_name = EMPATHY_IMAGE_MESSAGE;
688 else if (remote_contact && empathy_chat_is_composing (chat)) {
689 icon_name = EMPATHY_IMAGE_TYPING;
691 else if (empathy_chat_is_sms_channel (chat)) {
692 icon_name = EMPATHY_IMAGE_SMS;
694 else if (remote_contact) {
695 icon_name = empathy_icon_name_for_contact (remote_contact);
696 } else {
697 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
700 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
701 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
702 if (icon_name != NULL) {
703 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
704 gtk_widget_show (tab_image);
705 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
706 gtk_widget_show (menu_image);
707 } else {
708 gtk_widget_hide (tab_image);
709 gtk_widget_hide (menu_image);
712 /* Update the sending spinner */
713 nb_sending = empathy_chat_get_n_messages_sending (chat);
714 sending_spinner = g_object_get_data (G_OBJECT (chat),
715 "chat-window-tab-sending-spinner");
717 g_object_set (sending_spinner,
718 "active", nb_sending > 0,
719 "visible", nb_sending > 0,
720 NULL);
722 /* Update tab tooltip */
723 tooltip = g_string_new (NULL);
725 if (remote_contact) {
726 id = empathy_contact_get_id (remote_contact);
727 status = empathy_contact_get_presence_message (remote_contact);
728 } else {
729 id = name;
732 if (empathy_chat_is_sms_channel (chat)) {
733 append_markup_printf (tooltip, "%s ", _("SMS:"));
736 append_markup_printf (tooltip,
737 "<b>%s</b><small> (%s)</small>",
739 tp_account_get_display_name (account));
741 if (nb_sending > 0) {
742 char *tmp = g_strdup_printf (
743 ngettext ("Sending %d message",
744 "Sending %d messages",
745 nb_sending),
746 nb_sending);
748 g_string_append (tooltip, "\n");
749 g_string_append (tooltip, tmp);
751 gtk_widget_set_tooltip_text (sending_spinner, tmp);
752 g_free (tmp);
755 if (!EMP_STR_EMPTY (status)) {
756 append_markup_printf (tooltip, "\n<i>%s</i>", status);
759 if (subject) {
760 append_markup_printf (tooltip, "\n<b>%s</b> %s",
761 _("Topic:"), subject);
764 if (remote_contact && empathy_chat_is_composing (chat)) {
765 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
768 markup = g_string_free (tooltip, FALSE);
769 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
770 gtk_widget_set_tooltip_markup (widget, markup);
771 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
772 gtk_widget_set_tooltip_markup (widget, markup);
773 g_free (markup);
775 /* Update tab and menu label */
776 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
777 gtk_label_set_text (GTK_LABEL (widget), name);
778 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
779 gtk_label_set_text (GTK_LABEL (widget), name);
781 /* Update the window if it's the current chat */
782 if (priv->current_chat == chat) {
783 chat_window_update (window, update_contact_menu);
786 g_free (name);
789 static void
790 chat_window_update_chat_tab (EmpathyChat *chat)
792 chat_window_update_chat_tab_full (chat, TRUE);
795 static void
796 chat_window_chat_notify_cb (EmpathyChat *chat)
798 EmpathyChatWindow *window;
799 EmpathyContact *old_remote_contact;
800 EmpathyContact *remote_contact = NULL;
802 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
803 remote_contact = empathy_chat_get_remote_contact (chat);
805 if (old_remote_contact != remote_contact) {
806 /* The remote-contact associated with the chat changed, we need
807 * to keep track of any change of that contact and update the
808 * window each time. */
809 if (remote_contact) {
810 g_signal_connect_swapped (remote_contact, "notify",
811 G_CALLBACK (chat_window_update_chat_tab),
812 chat);
814 if (old_remote_contact) {
815 g_signal_handlers_disconnect_by_func (old_remote_contact,
816 chat_window_update_chat_tab,
817 chat);
820 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
821 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
824 chat_window_update_chat_tab (chat);
826 window = chat_window_find_chat (chat);
827 if (window != NULL) {
828 chat_window_update (window, FALSE);
832 static void
833 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
834 EmpathySmiley *smiley,
835 gpointer window)
837 EmpathyChatWindowPriv *priv = GET_PRIV (window);
838 EmpathyChat *chat;
839 GtkTextBuffer *buffer;
840 GtkTextIter iter;
842 chat = priv->current_chat;
844 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
845 gtk_text_buffer_get_end_iter (buffer, &iter);
846 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
849 static void
850 chat_window_conv_activate_cb (GtkAction *action,
851 EmpathyChatWindow *window)
853 EmpathyChatWindowPriv *priv = GET_PRIV (window);
854 gboolean is_room;
855 gboolean active;
856 EmpathyContact *remote_contact = NULL;
858 /* Favorite room menu */
859 is_room = empathy_chat_is_room (priv->current_chat);
860 if (is_room) {
861 const gchar *room;
862 TpAccount *account;
863 gboolean found = FALSE;
864 EmpathyChatroom *chatroom;
866 room = empathy_chat_get_id (priv->current_chat);
867 account = empathy_chat_get_account (priv->current_chat);
868 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
869 account, room);
870 if (chatroom != NULL)
871 found = empathy_chatroom_is_favorite (chatroom);
873 DEBUG ("This room %s favorite", found ? "is" : "is not");
874 gtk_toggle_action_set_active (
875 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
877 if (chatroom != NULL)
878 found = empathy_chatroom_is_always_urgent (chatroom);
880 gtk_toggle_action_set_active (
881 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
882 found);
884 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
885 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
887 /* Show contacts menu */
888 g_object_get (priv->current_chat,
889 "remote-contact", &remote_contact,
890 "show-contacts", &active,
891 NULL);
892 if (remote_contact == NULL) {
893 gtk_toggle_action_set_active (
894 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
895 active);
897 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
898 (remote_contact == NULL));
899 if (remote_contact != NULL) {
900 g_object_unref (remote_contact);
904 static void
905 chat_window_clear_activate_cb (GtkAction *action,
906 EmpathyChatWindow *window)
908 EmpathyChatWindowPriv *priv = GET_PRIV (window);
910 empathy_chat_clear (priv->current_chat);
913 static void
914 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
915 EmpathyChatWindow *window)
917 EmpathyChatWindowPriv *priv = GET_PRIV (window);
918 gboolean active;
919 TpAccount *account;
920 gchar *name;
921 const gchar *room;
922 EmpathyChatroom *chatroom;
924 active = gtk_toggle_action_get_active (toggle_action);
925 account = empathy_chat_get_account (priv->current_chat);
926 room = empathy_chat_get_id (priv->current_chat);
927 name = empathy_chat_dup_name (priv->current_chat);
929 chatroom = empathy_chatroom_manager_ensure_chatroom (
930 priv->chatroom_manager,
931 account,
932 room,
933 name);
935 empathy_chatroom_set_favorite (chatroom, active);
936 g_object_unref (chatroom);
937 g_free (name);
940 static void
941 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
942 EmpathyChatWindow *window)
944 EmpathyChatWindowPriv *priv = GET_PRIV (window);
945 gboolean active;
946 TpAccount *account;
947 gchar *name;
948 const gchar *room;
949 EmpathyChatroom *chatroom;
951 active = gtk_toggle_action_get_active (toggle_action);
952 account = empathy_chat_get_account (priv->current_chat);
953 room = empathy_chat_get_id (priv->current_chat);
954 name = empathy_chat_dup_name (priv->current_chat);
956 chatroom = empathy_chatroom_manager_ensure_chatroom (
957 priv->chatroom_manager,
958 account,
959 room,
960 name);
962 empathy_chatroom_set_always_urgent (chatroom, active);
963 g_object_unref (chatroom);
964 g_free (name);
967 static void
968 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
969 EmpathyChatWindow *window)
971 EmpathyChatWindowPriv *priv = GET_PRIV (window);
972 gboolean active;
974 active = gtk_toggle_action_get_active (toggle_action);
976 empathy_chat_set_show_contacts (priv->current_chat, active);
979 static void
980 chat_window_invite_participant_activate_cb (GtkAction *action,
981 EmpathyChatWindow *window)
983 EmpathyChatWindowPriv *priv;
984 GtkWidget *dialog;
985 EmpathyTpChat *tp_chat;
986 int response;
988 priv = GET_PRIV (window);
990 g_return_if_fail (priv->current_chat != NULL);
992 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
994 dialog = empathy_invite_participant_dialog_new (
995 GTK_WINDOW (priv->dialog), tp_chat);
996 gtk_widget_show (dialog);
998 response = gtk_dialog_run (GTK_DIALOG (dialog));
1000 if (response == GTK_RESPONSE_ACCEPT) {
1001 TpContact *tp_contact;
1002 EmpathyContact *contact;
1004 tp_contact = empathy_invite_participant_dialog_get_selected (
1005 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1006 if (tp_contact == NULL) goto out;
1008 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1010 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1011 contact, _("Inviting you to this room"));
1013 g_object_unref (contact);
1016 out:
1017 gtk_widget_destroy (dialog);
1020 static void
1021 chat_window_close_activate_cb (GtkAction *action,
1022 EmpathyChatWindow *window)
1024 EmpathyChatWindowPriv *priv;
1026 priv = GET_PRIV (window);
1028 g_return_if_fail (priv->current_chat != NULL);
1030 empathy_chat_window_remove_chat (window, priv->current_chat);
1033 static void
1034 chat_window_edit_activate_cb (GtkAction *action,
1035 EmpathyChatWindow *window)
1037 EmpathyChatWindowPriv *priv;
1038 GtkClipboard *clipboard;
1039 GtkTextBuffer *buffer;
1040 gboolean text_available;
1042 priv = GET_PRIV (window);
1044 g_return_if_fail (priv->current_chat != NULL);
1046 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1047 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1048 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1049 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1050 return;
1053 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1054 if (gtk_text_buffer_get_has_selection (buffer)) {
1055 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1056 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1057 } else {
1058 gboolean selection;
1060 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1062 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1063 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1066 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1067 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1068 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1071 static void
1072 chat_window_cut_activate_cb (GtkAction *action,
1073 EmpathyChatWindow *window)
1075 EmpathyChatWindowPriv *priv;
1077 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1079 priv = GET_PRIV (window);
1081 empathy_chat_cut (priv->current_chat);
1084 static void
1085 chat_window_copy_activate_cb (GtkAction *action,
1086 EmpathyChatWindow *window)
1088 EmpathyChatWindowPriv *priv;
1090 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1092 priv = GET_PRIV (window);
1094 empathy_chat_copy (priv->current_chat);
1097 static void
1098 chat_window_paste_activate_cb (GtkAction *action,
1099 EmpathyChatWindow *window)
1101 EmpathyChatWindowPriv *priv;
1103 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1105 priv = GET_PRIV (window);
1107 empathy_chat_paste (priv->current_chat);
1110 static void
1111 chat_window_find_activate_cb (GtkAction *action,
1112 EmpathyChatWindow *window)
1114 EmpathyChatWindowPriv *priv;
1116 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1118 priv = GET_PRIV (window);
1120 empathy_chat_find (priv->current_chat);
1123 static void
1124 chat_window_tabs_next_activate_cb (GtkAction *action,
1125 EmpathyChatWindow *window)
1127 EmpathyChatWindowPriv *priv;
1128 gint index_, numPages;
1129 gboolean wrap_around;
1131 priv = GET_PRIV (window);
1133 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1134 &wrap_around, NULL);
1136 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1137 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1139 if (index_ == (numPages - 1) && wrap_around) {
1140 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1141 return;
1144 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1147 static void
1148 chat_window_tabs_previous_activate_cb (GtkAction *action,
1149 EmpathyChatWindow *window)
1151 EmpathyChatWindowPriv *priv;
1152 gint index_, numPages;
1153 gboolean wrap_around;
1155 priv = GET_PRIV (window);
1157 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1158 &wrap_around, NULL);
1160 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1161 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1163 if (index_ <= 0 && wrap_around) {
1164 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1165 return;
1168 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1171 static void
1172 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1173 EmpathyChatWindow *window)
1175 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1176 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1177 empathy_get_current_action_time ());
1180 static void
1181 chat_window_tabs_left_activate_cb (GtkAction *action,
1182 EmpathyChatWindow *window)
1184 EmpathyChatWindowPriv *priv;
1185 EmpathyChat *chat;
1186 gint index_, num_pages;
1188 priv = GET_PRIV (window);
1190 chat = priv->current_chat;
1191 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1192 if (index_ <= 0) {
1193 return;
1196 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1197 GTK_WIDGET (chat),
1198 index_ - 1);
1200 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1201 chat_window_menu_context_update (priv, num_pages);
1204 static void
1205 chat_window_tabs_right_activate_cb (GtkAction *action,
1206 EmpathyChatWindow *window)
1208 EmpathyChatWindowPriv *priv;
1209 EmpathyChat *chat;
1210 gint index_, num_pages;
1212 priv = GET_PRIV (window);
1214 chat = priv->current_chat;
1215 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1217 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1218 GTK_WIDGET (chat),
1219 index_ + 1);
1221 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1222 chat_window_menu_context_update (priv, num_pages);
1225 static EmpathyChatWindow *
1226 empathy_chat_window_new (void)
1228 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1231 static void
1232 chat_window_detach_activate_cb (GtkAction *action,
1233 EmpathyChatWindow *window)
1235 EmpathyChatWindowPriv *priv;
1236 EmpathyChatWindow *new_window;
1237 EmpathyChat *chat;
1239 priv = GET_PRIV (window);
1241 chat = priv->current_chat;
1242 new_window = empathy_chat_window_new ();
1244 empathy_chat_window_move_chat (window, new_window, chat);
1246 priv = GET_PRIV (new_window);
1247 gtk_widget_show (priv->dialog);
1250 static void
1251 chat_window_help_contents_activate_cb (GtkAction *action,
1252 EmpathyChatWindow *window)
1254 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1256 empathy_url_show (priv->dialog, "ghelp:empathy");
1259 static void
1260 chat_window_help_about_activate_cb (GtkAction *action,
1261 EmpathyChatWindow *window)
1263 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1265 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1268 static gboolean
1269 chat_window_delete_event_cb (GtkWidget *dialog,
1270 GdkEvent *event,
1271 EmpathyChatWindow *window)
1273 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1275 DEBUG ("Delete event received");
1277 g_object_ref (window);
1278 while (priv->chats) {
1279 empathy_chat_window_remove_chat (window, priv->chats->data);
1281 g_object_unref (window);
1283 return TRUE;
1286 static void
1287 chat_window_composing_cb (EmpathyChat *chat,
1288 gboolean is_composing,
1289 EmpathyChatWindow *window)
1291 chat_window_update_chat_tab (chat);
1294 static void
1295 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1296 gboolean urgent)
1298 EmpathyChatWindowPriv *priv;
1300 priv = GET_PRIV (window);
1302 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1305 static void
1306 chat_window_notification_closed_cb (NotifyNotification *notify,
1307 EmpathyChatWindow *self)
1309 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1311 g_object_unref (notify);
1312 if (priv->notification == notify) {
1313 priv->notification = NULL;
1317 static void
1318 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1319 EmpathyMessage *message,
1320 EmpathyChat *chat)
1322 EmpathyContact *sender;
1323 const gchar *header;
1324 char *escaped;
1325 const char *body;
1326 GdkPixbuf *pixbuf;
1327 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1328 gboolean res, has_x_canonical_append;
1329 NotifyNotification *notification = priv->notification;
1331 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1332 return;
1333 } else {
1334 res = g_settings_get_boolean (priv->gsettings_notif,
1335 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1337 if (!res) {
1338 return;
1342 sender = empathy_message_get_sender (message);
1343 header = empathy_contact_get_alias (sender);
1344 body = empathy_message_get_body (message);
1345 escaped = g_markup_escape_text (body, -1);
1346 has_x_canonical_append = empathy_notify_manager_has_capability (
1347 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1349 if (notification != NULL && !has_x_canonical_append) {
1350 /* if the notification server supports x-canonical-append, it is
1351 better to not use notify_notification_update to avoid
1352 overwriting the current notification message */
1353 notify_notification_update (notification,
1354 header, escaped, NULL);
1355 } else {
1356 /* if the notification server supports x-canonical-append,
1357 the hint will be added, so that the message from the
1358 just created notification will be automatically appended
1359 to an existing notification with the same title.
1360 In this way the previous message will not be lost: the new
1361 message will appear below it, in the same notification */
1362 notification = notify_notification_new (header, escaped, NULL);
1364 if (priv->notification == NULL) {
1365 priv->notification = notification;
1368 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1370 tp_g_signal_connect_object (notification, "closed",
1371 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1373 if (has_x_canonical_append) {
1374 /* We have to set a not empty string to keep libnotify happy */
1375 notify_notification_set_hint_string (notification,
1376 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1379 notify_notification_set_hint (notification,
1380 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1381 g_variant_new_string ("im.received"));
1384 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1385 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1387 if (pixbuf != NULL) {
1388 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1389 g_object_unref (pixbuf);
1392 notify_notification_show (notification, NULL);
1394 g_free (escaped);
1397 static void
1398 chat_window_set_highlight_room_labels (EmpathyChat *chat)
1400 gchar *markup, *name;
1401 GtkWidget *widget;
1403 if (!empathy_chat_is_room (chat))
1404 return;
1406 name = empathy_chat_dup_name (chat);
1407 markup = g_markup_printf_escaped (
1408 "<span color=\"red\" weight=\"bold\">%s</span>",
1409 name);
1411 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1412 gtk_label_set_markup (GTK_LABEL (widget), markup);
1414 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1415 gtk_label_set_markup (GTK_LABEL (widget), markup);
1417 g_free (name);
1418 g_free (markup);
1421 static gboolean
1422 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1424 EmpathyChatWindowPriv *priv;
1425 gboolean has_focus;
1427 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1429 priv = GET_PRIV (window);
1431 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1433 return has_focus;
1436 static void
1437 chat_window_new_message_cb (EmpathyChat *chat,
1438 EmpathyMessage *message,
1439 gboolean pending,
1440 EmpathyChatWindow *window)
1442 EmpathyChatWindowPriv *priv;
1443 gboolean has_focus;
1444 gboolean needs_urgency;
1445 EmpathyContact *sender;
1447 priv = GET_PRIV (window);
1449 has_focus = empathy_chat_window_has_focus (window);
1451 /* - if we're the sender, we play the sound if it's specified in the
1452 * preferences and we're not away.
1453 * - if we receive a message, we play the sound if it's specified in the
1454 * preferences and the window does not have focus on the chat receiving
1455 * the message.
1458 sender = empathy_message_get_sender (message);
1460 if (empathy_contact_is_user (sender)) {
1461 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1462 EMPATHY_SOUND_MESSAGE_OUTGOING);
1465 if (has_focus && priv->current_chat == chat) {
1466 /* window and tab are focused so consider the message to be read */
1468 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1469 empathy_chat_messages_read (chat);
1470 return;
1473 /* Update the chat tab if this is the first unread message */
1474 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1475 chat_window_update_chat_tab (chat);
1478 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1479 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1480 * an unamed MUC (msn-like).
1481 * In case of a MUC, we set urgency if either:
1482 * a) the chatroom's always_urgent property is TRUE
1483 * b) the message contains our alias
1485 if (empathy_chat_is_room (chat) ||
1486 empathy_chat_get_remote_contact (chat) == NULL) {
1487 TpAccount *account;
1488 const gchar *room;
1489 EmpathyChatroom *chatroom;
1491 account = empathy_chat_get_account (chat);
1492 room = empathy_chat_get_id (chat);
1494 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1495 account, room);
1497 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1498 needs_urgency = TRUE;
1499 } else {
1500 needs_urgency = empathy_message_should_highlight (message);
1502 } else {
1503 needs_urgency = TRUE;
1506 if (needs_urgency) {
1507 chat_window_set_highlight_room_labels (chat);
1509 if (!has_focus) {
1510 chat_window_set_urgency_hint (window, TRUE);
1513 /* Pending messages have already been displayed and notified in the
1514 * approver, so we don't display a notification and play a sound for those */
1515 if (!pending) {
1516 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1517 EMPATHY_SOUND_MESSAGE_INCOMING);
1519 chat_window_show_or_update_notification (window, message, chat);
1523 /* update the number of unread messages and the window icon */
1524 chat_window_title_update (priv);
1525 chat_window_icon_update (priv, TRUE);
1528 static void
1529 chat_window_command_part (EmpathyChat *chat,
1530 GStrv strv)
1532 EmpathyChat *chat_to_be_parted;
1533 EmpathyTpChat *tp_chat = NULL;
1535 if (strv[1] == NULL) {
1536 /* No chatroom ID specified */
1537 tp_chat = empathy_chat_get_tp_chat (chat);
1538 if (tp_chat)
1539 empathy_tp_chat_leave (tp_chat, "");
1540 return;
1542 chat_to_be_parted = empathy_chat_window_find_chat (
1543 empathy_chat_get_account (chat), strv[1], FALSE);
1545 if (chat_to_be_parted != NULL) {
1546 /* Found a chatroom matching the specified ID */
1547 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1548 if (tp_chat)
1549 empathy_tp_chat_leave (tp_chat, strv[2]);
1550 } else {
1551 gchar *message;
1553 /* Going by the syntax of PART command:
1555 * /PART [<chatroom-ID>] [<reason>]
1557 * Chatroom-ID is not a must to specify a reason.
1558 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1559 * MUC then the current chatroom should be parted and srtv[1] should
1560 * be treated as part of the optional part-message. */
1561 message = g_strconcat (strv[1], " ", strv[2], NULL);
1562 tp_chat = empathy_chat_get_tp_chat (chat);
1563 if (tp_chat)
1564 empathy_tp_chat_leave (tp_chat, message);
1566 g_free (message);
1570 static GtkNotebook *
1571 notebook_create_window_cb (GtkNotebook *source,
1572 GtkWidget *page,
1573 gint x,
1574 gint y,
1575 gpointer user_data)
1577 EmpathyChatWindowPriv *priv;
1578 EmpathyChatWindow *window, *new_window;
1579 EmpathyChat *chat;
1581 chat = EMPATHY_CHAT (page);
1582 window = chat_window_find_chat (chat);
1584 new_window = empathy_chat_window_new ();
1585 priv = GET_PRIV (new_window);
1587 DEBUG ("Detach hook called");
1589 empathy_chat_window_move_chat (window, new_window, chat);
1591 gtk_widget_show (priv->dialog);
1592 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1594 return NULL;
1597 static void
1598 chat_window_page_switched_cb (GtkNotebook *notebook,
1599 GtkWidget *child,
1600 gint page_num,
1601 EmpathyChatWindow *window)
1603 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1604 EmpathyChat *chat = EMPATHY_CHAT (child);
1606 DEBUG ("Page switched");
1608 if (priv->page_added) {
1609 priv->page_added = FALSE;
1610 empathy_chat_scroll_down (chat);
1612 else if (priv->current_chat == chat) {
1613 return;
1616 priv->current_chat = chat;
1617 empathy_chat_messages_read (chat);
1619 chat_window_update_chat_tab (chat);
1622 static void
1623 chat_window_page_added_cb (GtkNotebook *notebook,
1624 GtkWidget *child,
1625 guint page_num,
1626 EmpathyChatWindow *window)
1628 EmpathyChatWindowPriv *priv;
1629 EmpathyChat *chat;
1631 priv = GET_PRIV (window);
1633 /* If we just received DND to the same window, we don't want
1634 * to do anything here like removing the tab and then readding
1635 * it, so we return here and in "page-added".
1637 if (priv->dnd_same_window) {
1638 DEBUG ("Page added (back to the same window)");
1639 priv->dnd_same_window = FALSE;
1640 return;
1643 DEBUG ("Page added");
1645 /* Get chat object */
1646 chat = EMPATHY_CHAT (child);
1648 /* Connect chat signals for this window */
1649 g_signal_connect (chat, "composing",
1650 G_CALLBACK (chat_window_composing_cb),
1651 window);
1652 g_signal_connect (chat, "new-message",
1653 G_CALLBACK (chat_window_new_message_cb),
1654 window);
1655 g_signal_connect (chat, "part-command-entered",
1656 G_CALLBACK (chat_window_command_part),
1657 NULL);
1658 g_signal_connect (chat, "notify::tp-chat",
1659 G_CALLBACK (chat_window_update_chat_tab),
1660 window);
1662 /* Set flag so we know to perform some special operations on
1663 * switch page due to the new page being added.
1665 priv->page_added = TRUE;
1667 /* Get list of chats up to date */
1668 priv->chats = g_list_append (priv->chats, chat);
1670 chat_window_update_chat_tab (chat);
1673 static void
1674 chat_window_page_removed_cb (GtkNotebook *notebook,
1675 GtkWidget *child,
1676 guint page_num,
1677 EmpathyChatWindow *window)
1679 EmpathyChatWindowPriv *priv;
1680 EmpathyChat *chat;
1682 priv = GET_PRIV (window);
1684 /* If we just received DND to the same window, we don't want
1685 * to do anything here like removing the tab and then readding
1686 * it, so we return here and in "page-added".
1688 if (priv->dnd_same_window) {
1689 DEBUG ("Page removed (and will be readded to same window)");
1690 return;
1693 DEBUG ("Page removed");
1695 /* Get chat object */
1696 chat = EMPATHY_CHAT (child);
1698 /* Disconnect all signal handlers for this chat and this window */
1699 g_signal_handlers_disconnect_by_func (chat,
1700 G_CALLBACK (chat_window_composing_cb),
1701 window);
1702 g_signal_handlers_disconnect_by_func (chat,
1703 G_CALLBACK (chat_window_new_message_cb),
1704 window);
1705 g_signal_handlers_disconnect_by_func (chat,
1706 G_CALLBACK (chat_window_update_chat_tab),
1707 window);
1709 /* Keep list of chats up to date */
1710 priv->chats = g_list_remove (priv->chats, chat);
1711 empathy_chat_messages_read (chat);
1713 if (priv->chats == NULL) {
1714 g_object_unref (window);
1715 } else {
1716 chat_window_update (window, TRUE);
1720 static gboolean
1721 chat_window_focus_in_event_cb (GtkWidget *widget,
1722 GdkEvent *event,
1723 EmpathyChatWindow *window)
1725 EmpathyChatWindowPriv *priv;
1727 priv = GET_PRIV (window);
1729 empathy_chat_messages_read (priv->current_chat);
1731 chat_window_set_urgency_hint (window, FALSE);
1733 /* Update the title, since we now mark all unread messages as read. */
1734 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1736 return FALSE;
1739 static gboolean
1740 chat_window_drag_drop (GtkWidget *widget,
1741 GdkDragContext *context,
1742 int x,
1743 int y,
1744 guint time_,
1745 EmpathyChatWindow *window)
1747 GdkAtom target;
1748 EmpathyChatWindowPriv *priv;
1750 priv = GET_PRIV (window);
1752 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1753 if (target == GDK_NONE)
1754 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1756 if (target != GDK_NONE) {
1757 gtk_drag_get_data (widget, context, target, time_);
1758 return TRUE;
1761 return FALSE;
1764 static gboolean
1765 chat_window_drag_motion (GtkWidget *widget,
1766 GdkDragContext *context,
1767 int x,
1768 int y,
1769 guint time_,
1770 EmpathyChatWindow *window)
1772 GdkAtom target;
1773 EmpathyChatWindowPriv *priv;
1775 priv = GET_PRIV (window);
1777 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1778 if (target != GDK_NONE) {
1779 /* This is a file drag. Ensure the contact is online and set the
1780 drag type to COPY. Note that it's possible that the tab will
1781 be switched by GTK+ after a timeout from drag_motion without
1782 getting another drag_motion to disable the drop. You have
1783 to hold your mouse really still.
1785 EmpathyContact *contact;
1787 priv = GET_PRIV (window);
1788 contact = empathy_chat_get_remote_contact (priv->current_chat);
1789 /* contact is NULL for multi-user chats. We don't do
1790 * file transfers to MUCs. We also don't send files
1791 * to offline contacts or contacts that don't support
1792 * file transfer.
1794 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1795 gdk_drag_status (context, 0, time_);
1796 return FALSE;
1798 if (!(empathy_contact_get_capabilities (contact)
1799 & EMPATHY_CAPABILITIES_FT)) {
1800 gdk_drag_status (context, 0, time_);
1801 return FALSE;
1803 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1804 return TRUE;
1807 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1808 if (target != GDK_NONE) {
1809 /* This is a drag of a contact from a contact list. Set to COPY.
1810 FIXME: If this drag is to a MUC window, it invites the user.
1811 Otherwise, it opens a chat. Should we use a different drag
1812 type for invites? Should we allow ASK?
1814 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1815 return TRUE;
1818 return FALSE;
1821 static void
1822 chat_window_drag_data_received (GtkWidget *widget,
1823 GdkDragContext *context,
1824 int x,
1825 int y,
1826 GtkSelectionData *selection,
1827 guint info,
1828 guint time_,
1829 EmpathyChatWindow *window)
1831 if (info == DND_DRAG_TYPE_CONTACT_ID) {
1832 EmpathyChat *chat = NULL;
1833 EmpathyChatWindow *old_window;
1834 TpAccount *account = NULL;
1835 EmpathyClientFactory *factory;
1836 const gchar *id;
1837 gchar **strv;
1838 const gchar *account_id;
1839 const gchar *contact_id;
1841 id = (const gchar*) gtk_selection_data_get_data (selection);
1843 factory = empathy_client_factory_dup ();
1845 DEBUG ("DND contact from roster with id:'%s'", id);
1847 strv = g_strsplit (id, ":", 2);
1848 if (g_strv_length (strv) == 2) {
1849 account_id = strv[0];
1850 contact_id = strv[1];
1851 account =
1852 tp_simple_client_factory_ensure_account (
1853 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
1854 NULL, NULL);
1856 g_object_unref (factory);
1857 if (account != NULL)
1858 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
1861 if (account == NULL) {
1862 g_strfreev (strv);
1863 gtk_drag_finish (context, FALSE, FALSE, time_);
1864 return;
1867 if (!chat) {
1868 empathy_chat_with_contact_id (
1869 account, contact_id, empathy_get_current_action_time ());
1871 g_strfreev (strv);
1872 return;
1874 g_strfreev (strv);
1876 old_window = chat_window_find_chat (chat);
1877 if (old_window) {
1878 if (old_window == window) {
1879 gtk_drag_finish (context, TRUE, FALSE, time_);
1880 return;
1883 empathy_chat_window_move_chat (old_window, window, chat);
1884 } else {
1885 empathy_chat_window_add_chat (window, chat);
1888 /* Added to take care of any outstanding chat events */
1889 empathy_chat_window_present_chat (chat,
1890 TP_USER_ACTION_TIME_NOT_USER_ACTION);
1892 /* We should return TRUE to remove the data when doing
1893 * GDK_ACTION_MOVE, but we don't here otherwise it has
1894 * weird consequences, and we handle that internally
1895 * anyway with add_chat () and remove_chat ().
1897 gtk_drag_finish (context, TRUE, FALSE, time_);
1899 else if (info == DND_DRAG_TYPE_URI_LIST) {
1900 EmpathyChatWindowPriv *priv;
1901 EmpathyContact *contact;
1902 const gchar *data;
1904 priv = GET_PRIV (window);
1905 contact = empathy_chat_get_remote_contact (priv->current_chat);
1907 /* contact is NULL when current_chat is a multi-user chat.
1908 * We don't do file transfers to MUCs, so just cancel the drag.
1910 if (contact == NULL) {
1911 gtk_drag_finish (context, TRUE, FALSE, time_);
1912 return;
1915 data = (const gchar *) gtk_selection_data_get_data (selection);
1916 empathy_send_file_from_uri_list (contact, data);
1918 gtk_drag_finish (context, TRUE, FALSE, time_);
1920 else if (info == DND_DRAG_TYPE_TAB) {
1921 EmpathyChat **chat;
1922 EmpathyChatWindow *old_window = NULL;
1924 DEBUG ("DND tab");
1926 chat = (void *) gtk_selection_data_get_data (selection);
1927 old_window = chat_window_find_chat (*chat);
1929 if (old_window) {
1930 EmpathyChatWindowPriv *priv;
1932 priv = GET_PRIV (window);
1933 priv->dnd_same_window = (old_window == window);
1934 DEBUG ("DND tab (within same window: %s)",
1935 priv->dnd_same_window ? "Yes" : "No");
1937 } else {
1938 DEBUG ("DND from unknown source");
1939 gtk_drag_finish (context, FALSE, FALSE, time_);
1943 static void
1944 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1945 guint num_chats_in_manager,
1946 EmpathyChatWindow *window)
1948 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1950 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1951 num_chats_in_manager > 0);
1954 static void
1955 chat_window_finalize (GObject *object)
1957 EmpathyChatWindow *window;
1958 EmpathyChatWindowPriv *priv;
1960 window = EMPATHY_CHAT_WINDOW (object);
1961 priv = GET_PRIV (window);
1963 DEBUG ("Finalized: %p", object);
1965 g_object_unref (priv->ui_manager);
1966 g_object_unref (priv->chatroom_manager);
1967 g_object_unref (priv->notify_mgr);
1968 g_object_unref (priv->gsettings_chat);
1969 g_object_unref (priv->gsettings_notif);
1970 g_object_unref (priv->gsettings_ui);
1971 g_object_unref (priv->sound_mgr);
1973 if (priv->notification != NULL) {
1974 notify_notification_close (priv->notification, NULL);
1975 priv->notification = NULL;
1978 if (priv->contact_targets) {
1979 gtk_target_list_unref (priv->contact_targets);
1981 if (priv->file_targets) {
1982 gtk_target_list_unref (priv->file_targets);
1985 if (priv->chat_manager) {
1986 g_signal_handler_disconnect (priv->chat_manager,
1987 priv->chat_manager_chats_changed_id);
1988 g_object_unref (priv->chat_manager);
1989 priv->chat_manager = NULL;
1992 chat_windows = g_list_remove (chat_windows, window);
1993 gtk_widget_destroy (priv->dialog);
1995 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1998 static void
1999 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2001 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2003 object_class->finalize = chat_window_finalize;
2005 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2008 static void
2009 empathy_chat_window_init (EmpathyChatWindow *window)
2011 GtkBuilder *gui;
2012 GtkAccelGroup *accel_group;
2013 GClosure *closure;
2014 GtkWidget *menu;
2015 GtkWidget *submenu;
2016 guint i;
2017 GtkWidget *chat_vbox;
2018 gchar *filename;
2019 EmpathySmileyManager *smiley_manager;
2020 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2021 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2023 window->priv = priv;
2024 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2025 gui = empathy_builder_get_file (filename,
2026 "chat_window", &priv->dialog,
2027 "chat_vbox", &chat_vbox,
2028 "ui_manager", &priv->ui_manager,
2029 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2030 "menu_conv_favorite", &priv->menu_conv_favorite,
2031 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2032 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2033 "menu_edit_cut", &priv->menu_edit_cut,
2034 "menu_edit_copy", &priv->menu_edit_copy,
2035 "menu_edit_paste", &priv->menu_edit_paste,
2036 "menu_edit_find", &priv->menu_edit_find,
2037 "menu_tabs_next", &priv->menu_tabs_next,
2038 "menu_tabs_prev", &priv->menu_tabs_prev,
2039 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2040 "menu_tabs_left", &priv->menu_tabs_left,
2041 "menu_tabs_right", &priv->menu_tabs_right,
2042 "menu_tabs_detach", &priv->menu_tabs_detach,
2043 NULL);
2044 g_free (filename);
2046 empathy_builder_connect (gui, window,
2047 "menu_conv", "activate", chat_window_conv_activate_cb,
2048 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2049 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2050 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2051 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2052 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2053 "menu_conv_close", "activate", chat_window_close_activate_cb,
2054 "menu_edit", "activate", chat_window_edit_activate_cb,
2055 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2056 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2057 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2058 "menu_edit_find", "activate", chat_window_find_activate_cb,
2059 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2060 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2061 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2062 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2063 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2064 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2065 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2066 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2067 NULL);
2069 g_object_ref (priv->ui_manager);
2070 g_object_unref (gui);
2072 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2073 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2074 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2075 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2077 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2079 priv->notebook = gtk_notebook_new ();
2081 g_signal_connect (priv->notebook, "create-window",
2082 G_CALLBACK (notebook_create_window_cb), window);
2084 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2085 "EmpathyChatWindow");
2086 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2087 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2088 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2089 gtk_widget_show (priv->notebook);
2091 /* Set up accels */
2092 accel_group = gtk_accel_group_new ();
2093 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2095 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2096 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2097 window,
2098 NULL);
2099 gtk_accel_group_connect (accel_group,
2100 tab_accel_keys[i],
2101 GDK_MOD1_MASK,
2103 closure);
2106 g_object_unref (accel_group);
2108 /* Set up drag target lists */
2109 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2110 G_N_ELEMENTS (drag_types_dest_contact));
2111 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2112 G_N_ELEMENTS (drag_types_dest_file));
2114 /* Set up smiley menu */
2115 smiley_manager = empathy_smiley_manager_dup_singleton ();
2116 submenu = empathy_smiley_menu_new (smiley_manager,
2117 chat_window_insert_smiley_activate_cb,
2118 window);
2119 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2120 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2121 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2122 g_object_unref (smiley_manager);
2124 /* Set up signals we can't do with ui file since we may need to
2125 * block/unblock them at some later stage.
2128 g_signal_connect (priv->dialog,
2129 "delete_event",
2130 G_CALLBACK (chat_window_delete_event_cb),
2131 window);
2132 g_signal_connect (priv->dialog,
2133 "focus_in_event",
2134 G_CALLBACK (chat_window_focus_in_event_cb),
2135 window);
2136 g_signal_connect_after (priv->notebook,
2137 "switch_page",
2138 G_CALLBACK (chat_window_page_switched_cb),
2139 window);
2140 g_signal_connect (priv->notebook,
2141 "page_added",
2142 G_CALLBACK (chat_window_page_added_cb),
2143 window);
2144 g_signal_connect (priv->notebook,
2145 "page_removed",
2146 G_CALLBACK (chat_window_page_removed_cb),
2147 window);
2149 /* Set up drag and drop */
2150 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2151 GTK_DEST_DEFAULT_HIGHLIGHT,
2152 drag_types_dest,
2153 G_N_ELEMENTS (drag_types_dest),
2154 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2156 /* connect_after to allow GtkNotebook's built-in tab switching */
2157 g_signal_connect_after (priv->notebook,
2158 "drag-motion",
2159 G_CALLBACK (chat_window_drag_motion),
2160 window);
2161 g_signal_connect (priv->notebook,
2162 "drag-data-received",
2163 G_CALLBACK (chat_window_drag_data_received),
2164 window);
2165 g_signal_connect (priv->notebook,
2166 "drag-drop",
2167 G_CALLBACK (chat_window_drag_drop),
2168 window);
2170 chat_windows = g_list_prepend (chat_windows, window);
2172 /* Set up private details */
2173 priv->chats = NULL;
2174 priv->current_chat = NULL;
2175 priv->notification = NULL;
2177 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2179 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2180 priv->chat_manager_chats_changed_id =
2181 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2182 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2183 window);
2185 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2186 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2187 window);
2190 /* Returns the window to open a new tab in if there is a suitable window,
2191 * otherwise, returns NULL indicating that a new window should be added.
2193 static EmpathyChatWindow *
2194 empathy_chat_window_get_default (gboolean room)
2196 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2197 GList *l;
2198 gboolean separate_windows = TRUE;
2200 separate_windows = g_settings_get_boolean (gsettings,
2201 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2203 g_object_unref (gsettings);
2205 if (separate_windows) {
2206 /* Always create a new window */
2207 return NULL;
2210 for (l = chat_windows; l; l = l->next) {
2211 EmpathyChatWindow *chat_window;
2212 guint nb_rooms, nb_private;
2214 chat_window = l->data;
2216 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2218 /* Skip the window if there aren't any rooms in it */
2219 if (room && nb_rooms == 0)
2220 continue;
2222 /* Skip the window if there aren't any 1-1 chats in it */
2223 if (!room && nb_private == 0)
2224 continue;
2226 return chat_window;
2229 return NULL;
2232 static void
2233 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2234 EmpathyChat *chat)
2236 EmpathyChatWindowPriv *priv;
2237 GtkWidget *label;
2238 GtkWidget *popup_label;
2239 GtkWidget *child;
2240 GValue value = { 0, };
2242 g_return_if_fail (window != NULL);
2243 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2245 priv = GET_PRIV (window);
2247 /* Reference the chat object */
2248 g_object_ref (chat);
2250 /* If this window has just been created, position it */
2251 if (priv->chats == NULL) {
2252 const gchar *name = "chat-window";
2253 gboolean separate_windows;
2255 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2256 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2258 if (empathy_chat_is_room (chat))
2259 name = "room-window";
2261 if (separate_windows) {
2262 gint x, y;
2264 /* Save current position of the window */
2265 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2267 /* First bind to the 'generic' name. So new window for which we didn't
2268 * save a geometry yet will have the geometry of the last saved
2269 * window (bgo #601191). */
2270 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2272 /* Restore previous position of the window so the newly created window
2273 * won't be in the same position as the latest saved window and so
2274 * completely hide it. */
2275 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2277 /* Then bind it to the name of the contact/room so we'll save the
2278 * geometry specific to this window */
2279 name = empathy_chat_get_id (chat);
2282 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2285 child = GTK_WIDGET (chat);
2286 label = chat_window_create_label (window, chat, TRUE);
2287 popup_label = chat_window_create_label (window, chat, FALSE);
2288 gtk_widget_show (child);
2290 g_signal_connect (chat, "notify::name",
2291 G_CALLBACK (chat_window_chat_notify_cb),
2292 NULL);
2293 g_signal_connect (chat, "notify::subject",
2294 G_CALLBACK (chat_window_chat_notify_cb),
2295 NULL);
2296 g_signal_connect (chat, "notify::remote-contact",
2297 G_CALLBACK (chat_window_chat_notify_cb),
2298 NULL);
2299 g_signal_connect (chat, "notify::sms-channel",
2300 G_CALLBACK (chat_window_chat_notify_cb),
2301 NULL);
2302 g_signal_connect (chat, "notify::n-messages-sending",
2303 G_CALLBACK (chat_window_chat_notify_cb),
2304 NULL);
2305 g_signal_connect (chat, "notify::nb-unread-messages",
2306 G_CALLBACK (chat_window_chat_notify_cb),
2307 NULL);
2308 chat_window_chat_notify_cb (chat);
2310 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2311 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2312 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2313 g_value_init (&value, G_TYPE_BOOLEAN);
2314 g_value_set_boolean (&value, TRUE);
2315 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2316 child, "tab-expand" , &value);
2317 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2318 child, "tab-fill" , &value);
2319 g_value_unset (&value);
2321 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2324 static void
2325 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2326 EmpathyChat *chat)
2328 EmpathyChatWindowPriv *priv;
2329 gint position;
2330 EmpathyContact *remote_contact;
2331 EmpathyChatManager *chat_manager;
2333 g_return_if_fail (window != NULL);
2334 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2336 priv = GET_PRIV (window);
2338 g_signal_handlers_disconnect_by_func (chat,
2339 chat_window_chat_notify_cb,
2340 NULL);
2341 remote_contact = g_object_get_data (G_OBJECT (chat),
2342 "chat-window-remote-contact");
2343 if (remote_contact) {
2344 g_signal_handlers_disconnect_by_func (remote_contact,
2345 chat_window_update_chat_tab,
2346 chat);
2349 chat_manager = empathy_chat_manager_dup_singleton ();
2350 empathy_chat_manager_closed_chat (chat_manager, chat);
2351 g_object_unref (chat_manager);
2353 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2354 GTK_WIDGET (chat));
2355 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2357 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2359 g_object_unref (chat);
2362 static void
2363 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2364 EmpathyChatWindow *new_window,
2365 EmpathyChat *chat)
2367 GtkWidget *widget;
2369 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2370 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2371 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2373 widget = GTK_WIDGET (chat);
2375 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2376 G_OBJECT (widget)->ref_count);
2378 /* We reference here to make sure we don't loose the widget
2379 * and the EmpathyChat object during the move.
2381 g_object_ref (chat);
2382 g_object_ref (widget);
2384 empathy_chat_window_remove_chat (old_window, chat);
2385 empathy_chat_window_add_chat (new_window, chat);
2387 g_object_unref (widget);
2388 g_object_unref (chat);
2391 static void
2392 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2393 EmpathyChat *chat)
2395 EmpathyChatWindowPriv *priv;
2396 gint page_num;
2398 g_return_if_fail (window != NULL);
2399 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2401 priv = GET_PRIV (window);
2403 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2404 GTK_WIDGET (chat));
2405 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2406 page_num);
2409 EmpathyChat *
2410 empathy_chat_window_find_chat (TpAccount *account,
2411 const gchar *id,
2412 gboolean sms_channel)
2414 GList *l;
2416 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2418 for (l = chat_windows; l; l = l->next) {
2419 EmpathyChatWindowPriv *priv;
2420 EmpathyChatWindow *window;
2421 GList *ll;
2423 window = l->data;
2424 priv = GET_PRIV (window);
2426 for (ll = priv->chats; ll; ll = ll->next) {
2427 EmpathyChat *chat;
2429 chat = ll->data;
2431 if (account == empathy_chat_get_account (chat) &&
2432 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2433 sms_channel == empathy_chat_is_sms_channel (chat)) {
2434 return chat;
2439 return NULL;
2442 void
2443 empathy_chat_window_present_chat (EmpathyChat *chat,
2444 gint64 timestamp)
2446 EmpathyChatWindow *window;
2447 EmpathyChatWindowPriv *priv;
2448 guint32 x_timestamp;
2450 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2452 window = chat_window_find_chat (chat);
2454 /* If the chat has no window, create one */
2455 if (window == NULL) {
2456 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2457 if (!window) {
2458 window = empathy_chat_window_new ();
2460 /* we want to display the newly created window even if we don't present
2461 * it */
2462 priv = GET_PRIV (window);
2463 gtk_widget_show (priv->dialog);
2466 empathy_chat_window_add_chat (window, chat);
2469 /* Don't force the window to show itself when it wasn't
2470 * an action by the user
2472 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2473 return;
2475 priv = GET_PRIV (window);
2477 if (x_timestamp != GDK_CURRENT_TIME) {
2478 /* Don't present or switch tab if the action was earlier than the
2479 * last actions X time, accounting for overflow and the first ever
2480 * presentation */
2482 if (priv->x_user_action_time != 0
2483 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2484 return;
2486 priv->x_user_action_time = x_timestamp;
2489 empathy_chat_window_switch_to_chat (window, chat);
2490 empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2491 x_timestamp);
2493 gtk_widget_grab_focus (chat->input_text_view);
2496 static void
2497 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2498 guint *nb_rooms,
2499 guint *nb_private)
2501 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2502 GList *l;
2503 guint _nb_rooms = 0, _nb_private = 0;
2505 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2506 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2507 _nb_rooms++;
2508 else
2509 _nb_private++;
2512 if (nb_rooms != NULL)
2513 *nb_rooms = _nb_rooms;
2514 if (nb_private != NULL)
2515 *nb_private = _nb_private;