Show MUC highlight notifications in Gnome Shell
[empathy-mirror.git] / src / empathy-chat-window.c
blob0008fed38a57ca8c85e2ecb8693ab96ae510f065
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>
50 #include <libempathy/empathy-individual-manager.h>
52 #include <libempathy-gtk/empathy-images.h>
53 #include <libempathy-gtk/empathy-contact-dialogs.h>
54 #include <libempathy-gtk/empathy-log-window.h>
55 #include <libempathy-gtk/empathy-geometry.h>
56 #include <libempathy-gtk/empathy-smiley-manager.h>
57 #include <libempathy-gtk/empathy-sound-manager.h>
58 #include <libempathy-gtk/empathy-ui-utils.h>
59 #include <libempathy-gtk/empathy-notify-manager.h>
61 #include "empathy-chat-manager.h"
62 #include "empathy-chat-window.h"
63 #include "empathy-about-dialog.h"
64 #include "empathy-invite-participant-dialog.h"
65 #include "gedit-close-button.h"
67 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
68 #include <libempathy/empathy-debug.h>
70 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
72 #define X_EARLIER_OR_EQL(t1, t2) \
73 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
74 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
77 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
78 typedef struct {
79 EmpathyChat *current_chat;
80 GList *chats;
81 gboolean page_added;
82 gboolean dnd_same_window;
83 EmpathyChatroomManager *chatroom_manager;
84 EmpathyNotifyManager *notify_mgr;
85 GtkWidget *dialog;
86 GtkWidget *notebook;
87 NotifyNotification *notification;
89 GtkTargetList *contact_targets;
90 GtkTargetList *file_targets;
92 EmpathyChatManager *chat_manager;
93 gulong chat_manager_chats_changed_id;
95 /* Menu items. */
96 GtkUIManager *ui_manager;
97 GtkAction *menu_conv_insert_smiley;
98 GtkAction *menu_conv_favorite;
99 GtkAction *menu_conv_always_urgent;
100 GtkAction *menu_conv_toggle_contacts;
102 GtkAction *menu_edit_cut;
103 GtkAction *menu_edit_copy;
104 GtkAction *menu_edit_paste;
105 GtkAction *menu_edit_find;
107 GtkAction *menu_tabs_next;
108 GtkAction *menu_tabs_prev;
109 GtkAction *menu_tabs_undo_close_tab;
110 GtkAction *menu_tabs_left;
111 GtkAction *menu_tabs_right;
112 GtkAction *menu_tabs_detach;
114 /* Last user action time we acted upon to show a tab */
115 guint32 x_user_action_time;
117 GSettings *gsettings_chat;
118 GSettings *gsettings_notif;
119 GSettings *gsettings_ui;
121 EmpathySoundManager *sound_mgr;
122 } EmpathyChatWindowPriv;
124 static GList *chat_windows = NULL;
126 static const guint tab_accel_keys[] = {
127 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
128 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
131 typedef enum {
132 DND_DRAG_TYPE_CONTACT_ID,
133 DND_DRAG_TYPE_INDIVIDUAL_ID,
134 DND_DRAG_TYPE_URI_LIST,
135 DND_DRAG_TYPE_TAB
136 } DndDragType;
138 static const GtkTargetEntry drag_types_dest[] = {
139 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
140 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
141 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
142 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
143 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
146 static const GtkTargetEntry drag_types_dest_contact[] = {
147 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
148 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
151 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,
162 EmpathyChat *chat);
164 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
165 EmpathyChat *chat);
167 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
168 EmpathyChatWindow *new_window,
169 EmpathyChat *chat);
171 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
172 guint *nb_rooms,
173 guint *nb_private);
175 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
177 static void
178 chat_window_accel_cb (GtkAccelGroup *accelgroup,
179 GObject *object,
180 guint key,
181 GdkModifierType mod,
182 EmpathyChatWindow *window)
184 EmpathyChatWindowPriv *priv;
185 gint num = -1;
186 guint i;
188 priv = GET_PRIV (window);
190 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
191 if (tab_accel_keys[i] == key) {
192 num = i;
193 break;
197 if (num != -1) {
198 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
202 static EmpathyChatWindow *
203 chat_window_find_chat (EmpathyChat *chat)
205 EmpathyChatWindowPriv *priv;
206 GList *l, *ll;
208 for (l = chat_windows; l; l = l->next) {
209 priv = GET_PRIV (l->data);
210 ll = g_list_find (priv->chats, chat);
211 if (ll) {
212 return l->data;
216 return NULL;
219 static void
220 remove_all_chats (EmpathyChatWindow *window)
222 EmpathyChatWindowPriv *priv;
224 priv = GET_PRIV (window);
225 g_object_ref (window);
227 while (priv->chats) {
228 empathy_chat_window_remove_chat (window, priv->chats->data);
231 g_object_unref (window);
234 static void
235 confirm_close_response_cb (GtkWidget *dialog,
236 int response,
237 EmpathyChatWindow *window)
239 EmpathyChat *chat;
241 chat = g_object_get_data (G_OBJECT (dialog), "chat");
243 gtk_widget_destroy (dialog);
245 if (response != GTK_RESPONSE_ACCEPT)
246 return;
248 if (chat != NULL) {
249 empathy_chat_window_remove_chat (window, chat);
250 } else {
251 remove_all_chats (window);
255 static void
256 confirm_close (EmpathyChatWindow *window,
257 gboolean close_window,
258 guint n_rooms,
259 EmpathyChat *chat)
261 EmpathyChatWindowPriv *priv;
262 GtkWidget *dialog;
263 gchar *primary, *secondary;
265 g_return_if_fail (n_rooms > 0);
267 if (n_rooms > 1) {
268 g_return_if_fail (chat == NULL);
269 } else {
270 g_return_if_fail (chat != NULL);
273 priv = GET_PRIV (window);
275 /* If there are no chats in this window, how could we possibly have got
276 * here?
278 g_return_if_fail (priv->chats != NULL);
280 /* Treat closing a window which only has one tab exactly like closing
281 * that tab.
283 if (close_window && priv->chats->next == NULL) {
284 close_window = FALSE;
285 chat = priv->chats->data;
288 if (close_window) {
289 primary = g_strdup (_("Close this window?"));
291 if (n_rooms == 1) {
292 gchar *chat_name = empathy_chat_dup_name (chat);
293 secondary = g_strdup_printf (
294 _("Closing this window will leave %s. You will "
295 "not receive any further messages until you "
296 "rejoin it."),
297 chat_name);
298 g_free (chat_name);
299 } else {
300 secondary = g_strdup_printf (
301 /* Note to translators: the number of chats will
302 * always be at least 2.
304 ngettext (
305 "Closing this window will leave a chat room. You will "
306 "not receive any further messages until you rejoin it.",
307 "Closing this window will leave %u chat rooms. You will "
308 "not receive any further messages until you rejoin them.",
309 n_rooms),
310 n_rooms);
312 } else {
313 gchar *chat_name = empathy_chat_dup_name (chat);
314 primary = g_strdup_printf (_("Leave %s?"), chat_name);
315 secondary = g_strdup (_("You will not receive any further messages from this chat "
316 "room until you rejoin it."));
317 g_free (chat_name);
320 dialog = gtk_message_dialog_new (
321 GTK_WINDOW (priv->dialog),
322 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
323 GTK_MESSAGE_WARNING,
324 GTK_BUTTONS_CANCEL,
325 "%s", primary);
327 gtk_window_set_title (GTK_WINDOW (dialog), "");
328 g_object_set (dialog, "secondary-text", secondary, NULL);
330 g_free (primary);
331 g_free (secondary);
333 gtk_dialog_add_button (GTK_DIALOG (dialog),
334 close_window ? _("Close window") : _("Leave room"),
335 GTK_RESPONSE_ACCEPT);
336 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
337 GTK_RESPONSE_ACCEPT);
339 if (!close_window) {
340 g_object_set_data (G_OBJECT (dialog), "chat", chat);
343 g_signal_connect (dialog, "response",
344 G_CALLBACK (confirm_close_response_cb), window);
346 gtk_window_present (GTK_WINDOW (dialog));
349 /* Returns TRUE if we should check if the user really wants to leave. If it's
350 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
351 * the user is actually in the room as opposed to having been kicked or gone
352 * offline or something), then we should check.
354 static gboolean
355 chat_needs_close_confirmation (EmpathyChat *chat)
357 return (empathy_chat_is_room (chat)
358 && empathy_chat_get_tp_chat (chat) != NULL);
361 static void
362 maybe_close_chat (EmpathyChatWindow *window,
363 EmpathyChat *chat)
365 g_return_if_fail (chat != NULL);
367 if (chat_needs_close_confirmation (chat)) {
368 confirm_close (window, FALSE, 1, chat);
369 } else {
370 empathy_chat_window_remove_chat (window, chat);
374 static void
375 chat_window_close_clicked_cb (GtkAction *action,
376 EmpathyChat *chat)
378 EmpathyChatWindow *window;
380 window = chat_window_find_chat (chat);
381 maybe_close_chat (window, chat);
384 static void
385 chat_tab_style_updated_cb (GtkWidget *hbox,
386 gpointer user_data)
388 GtkWidget *button;
389 int char_width, h, w;
390 PangoContext *context;
391 const PangoFontDescription *font_desc;
392 PangoFontMetrics *metrics;
394 button = g_object_get_data (G_OBJECT (user_data),
395 "chat-window-tab-close-button");
396 context = gtk_widget_get_pango_context (hbox);
398 font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
399 GTK_STATE_FLAG_NORMAL);
401 metrics = pango_context_get_metrics (context, font_desc,
402 pango_context_get_language (context));
403 char_width = pango_font_metrics_get_approximate_char_width (metrics);
404 pango_font_metrics_unref (metrics);
406 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
407 GTK_ICON_SIZE_MENU, &w, &h);
409 /* Request at least about 12 chars width plus at least space for the status
410 * image and the close button */
411 gtk_widget_set_size_request (hbox,
412 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
414 gtk_widget_set_size_request (button, w, h);
417 static GtkWidget *
418 chat_window_create_label (EmpathyChatWindow *window,
419 EmpathyChat *chat,
420 gboolean is_tab_label)
422 GtkWidget *hbox;
423 GtkWidget *name_label;
424 GtkWidget *status_image;
425 GtkWidget *event_box;
426 GtkWidget *event_box_hbox;
427 PangoAttrList *attr_list;
428 PangoAttribute *attr;
430 /* The spacing between the button and the label. */
431 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
433 event_box = gtk_event_box_new ();
434 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
436 name_label = gtk_label_new (NULL);
437 if (is_tab_label)
438 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
440 attr_list = pango_attr_list_new ();
441 attr = pango_attr_scale_new (1/1.2);
442 attr->start_index = 0;
443 attr->end_index = -1;
444 pango_attr_list_insert (attr_list, attr);
445 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
446 pango_attr_list_unref (attr_list);
448 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
449 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
450 g_object_set_data (G_OBJECT (chat),
451 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
452 name_label);
454 status_image = gtk_image_new ();
456 /* Spacing between the icon and label. */
457 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
459 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
460 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
462 g_object_set_data (G_OBJECT (chat),
463 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
464 status_image);
465 g_object_set_data (G_OBJECT (chat),
466 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
467 event_box);
469 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
470 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
472 if (is_tab_label) {
473 GtkWidget *close_button;
474 GtkWidget *sending_spinner;
476 sending_spinner = gtk_spinner_new ();
478 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
479 FALSE, FALSE, 0);
480 g_object_set_data (G_OBJECT (chat),
481 "chat-window-tab-sending-spinner",
482 sending_spinner);
484 close_button = gedit_close_button_new ();
485 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
487 /* We don't want focus/keynav for the button to avoid clutter, and
488 * Ctrl-W works anyway.
490 gtk_widget_set_can_focus (close_button, FALSE);
491 gtk_widget_set_can_default (close_button, FALSE);
493 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
495 g_signal_connect (close_button,
496 "clicked",
497 G_CALLBACK (chat_window_close_clicked_cb),
498 chat);
500 /* React to theme changes and also setup the size correctly. */
501 g_signal_connect (hbox,
502 "style-updated",
503 G_CALLBACK (chat_tab_style_updated_cb),
504 chat);
507 gtk_widget_show_all (hbox);
509 return hbox;
512 static void
513 _submenu_notify_visible_changed_cb (GObject *object,
514 GParamSpec *pspec,
515 gpointer userdata)
517 g_signal_handlers_disconnect_by_func (object,
518 _submenu_notify_visible_changed_cb,
519 userdata);
520 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
523 static void
524 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
525 gint num_pages)
527 gboolean first_page;
528 gboolean last_page;
529 gboolean wrap_around;
530 gboolean is_connected;
531 gint page_num;
533 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
534 first_page = (page_num == 0);
535 last_page = (page_num == (num_pages - 1));
536 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
537 &wrap_around, NULL);
538 is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
540 gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
541 wrap_around));
542 gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
543 wrap_around));
544 gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
545 gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
546 gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
547 gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
550 static void
551 chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv,
552 EmpathyChatWindow *self)
554 EmpathyTpChat *tp_chat;
555 TpConnection *connection;
556 GtkAction *action;
557 gboolean sensitive = FALSE;
559 g_return_if_fail (priv->current_chat != NULL);
561 action = gtk_ui_manager_get_action (priv->ui_manager,
562 "/chats_menubar/menu_conv/menu_conv_invite_participant");
563 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
565 if (tp_chat != NULL) {
566 connection = tp_channel_borrow_connection (TP_CHANNEL (tp_chat));
568 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
569 (tp_connection_get_status (connection, NULL) ==
570 TP_CONNECTION_STATUS_CONNECTED);
573 gtk_action_set_sensitive (action, sensitive);
576 static void
577 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
578 EmpathyChatWindow *window)
580 GtkWidget *menu, *submenu, *orig_submenu;
582 menu = gtk_ui_manager_get_widget (priv->ui_manager,
583 "/chats_menubar/menu_contact");
584 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
586 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
587 submenu = empathy_chat_get_contact_menu (priv->current_chat);
589 if (submenu != NULL) {
590 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
591 g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
593 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
594 gtk_widget_show (menu);
595 gtk_widget_set_sensitive (menu, TRUE);
596 } else {
597 gtk_widget_set_sensitive (menu, FALSE);
599 } else {
600 tp_g_signal_connect_object (orig_submenu,
601 "notify::visible",
602 (GCallback)_submenu_notify_visible_changed_cb,
603 window, 0);
607 static guint
608 get_all_unread_messages (EmpathyChatWindowPriv *priv)
610 GList *l;
611 guint nb = 0;
613 for (l = priv->chats; l != NULL; l = g_list_next (l))
614 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
616 return nb;
619 static gchar *
620 get_window_title_name (EmpathyChatWindowPriv *priv)
622 gchar *active_name, *ret;
623 guint nb_chats;
624 guint current_unread_msgs;
626 nb_chats = g_list_length (priv->chats);
627 g_assert (nb_chats > 0);
629 active_name = empathy_chat_dup_name (priv->current_chat);
631 current_unread_msgs = empathy_chat_get_nb_unread_messages (
632 priv->current_chat);
634 if (nb_chats == 1) {
635 /* only one tab */
636 if (current_unread_msgs == 0)
637 ret = g_strdup (active_name);
638 else
639 ret = g_strdup_printf (ngettext (
640 "%s (%d unread)",
641 "%s (%d unread)", current_unread_msgs),
642 active_name, current_unread_msgs);
643 } else {
644 guint nb_others = nb_chats - 1;
645 guint all_unread_msgs;
647 all_unread_msgs = get_all_unread_messages (priv);
649 if (all_unread_msgs == 0) {
650 /* no unread message */
651 ret = g_strdup_printf (ngettext (
652 "%s (and %u other)",
653 "%s (and %u others)", nb_others),
654 active_name, nb_others);
657 else if (all_unread_msgs == current_unread_msgs) {
658 /* unread messages are in the current tab */
659 ret = g_strdup_printf (ngettext (
660 "%s (%d unread)",
661 "%s (%d unread)", current_unread_msgs),
662 active_name, current_unread_msgs);
665 else if (current_unread_msgs == 0) {
666 /* unread messages are in other tabs */
667 ret = g_strdup_printf (ngettext (
668 "%s (%d unread from others)",
669 "%s (%d unread from others)",
670 all_unread_msgs),
671 active_name, all_unread_msgs);
674 else {
675 /* unread messages are in all the tabs */
676 ret = g_strdup_printf (ngettext (
677 "%s (%d unread from all)",
678 "%s (%d unread from all)",
679 all_unread_msgs),
680 active_name, all_unread_msgs);
684 g_free (active_name);
686 return ret;
689 static void
690 chat_window_title_update (EmpathyChatWindowPriv *priv)
692 gchar *name;
694 name = get_window_title_name (priv);
695 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
696 g_free (name);
699 static void
700 chat_window_icon_update (EmpathyChatWindowPriv *priv, gboolean new_messages)
702 GdkPixbuf *icon;
703 EmpathyContact *remote_contact;
704 gboolean avatar_in_icon;
705 guint n_chats;
707 n_chats = g_list_length (priv->chats);
709 /* Update window icon */
710 if (new_messages) {
711 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
712 EMPATHY_IMAGE_MESSAGE);
713 } else {
714 avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
715 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
717 if (n_chats == 1 && avatar_in_icon) {
718 remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
719 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
720 gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
722 if (icon != NULL) {
723 g_object_unref (icon);
725 } else {
726 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
731 static void
732 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
733 gint num_pages)
735 GtkWidget *chat;
736 GtkWidget *chat_close_button;
737 gint i;
739 if (num_pages == 1) {
740 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
741 chat_close_button = g_object_get_data (G_OBJECT (chat),
742 "chat-window-tab-close-button");
743 gtk_widget_hide (chat_close_button);
744 } else {
745 for (i=0; i<num_pages; i++) {
746 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
747 chat_close_button = g_object_get_data (G_OBJECT (chat),
748 "chat-window-tab-close-button");
749 gtk_widget_show (chat_close_button);
754 static void
755 chat_window_update (EmpathyChatWindow *window,
756 gboolean update_contact_menu)
758 EmpathyChatWindowPriv *priv = GET_PRIV (window);
759 gint num_pages;
761 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
763 /* Update Tab menu */
764 chat_window_menu_context_update (priv,
765 num_pages);
767 chat_window_conversation_menu_update (priv, window);
769 /* If this update is due to a focus-in event, we know the menu will be
770 the same as when we last left it, so no work to do. Besides, if we
771 swap out the menu on a focus-in, we may confuse any external global
772 menu watching. */
773 if (update_contact_menu) {
774 chat_window_contact_menu_update (priv,
775 window);
778 chat_window_title_update (priv);
780 chat_window_icon_update (priv, get_all_unread_messages (priv) > 0);
782 chat_window_close_button_update (priv,
783 num_pages);
786 static void
787 append_markup_printf (GString *string,
788 const char *format,
789 ...)
791 gchar *tmp;
792 va_list args;
794 va_start (args, format);
796 tmp = g_markup_vprintf_escaped (format, args);
797 g_string_append (string, tmp);
798 g_free (tmp);
800 va_end (args);
803 static void
804 chat_window_update_chat_tab_full (EmpathyChat *chat,
805 gboolean update_contact_menu)
807 EmpathyChatWindow *window;
808 EmpathyChatWindowPriv *priv;
809 EmpathyContact *remote_contact;
810 gchar *name;
811 const gchar *id;
812 TpAccount *account;
813 const gchar *subject;
814 const gchar *status = NULL;
815 GtkWidget *widget;
816 GString *tooltip;
817 gchar *markup;
818 const gchar *icon_name;
819 GtkWidget *tab_image;
820 GtkWidget *menu_image;
821 GtkWidget *sending_spinner;
822 guint nb_sending;
824 window = chat_window_find_chat (chat);
825 if (!window) {
826 return;
828 priv = GET_PRIV (window);
830 /* Get information */
831 name = empathy_chat_dup_name (chat);
832 account = empathy_chat_get_account (chat);
833 subject = empathy_chat_get_subject (chat);
834 remote_contact = empathy_chat_get_remote_contact (chat);
836 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
837 name, tp_proxy_get_object_path (account), subject, remote_contact);
839 /* Update tab image */
840 if (empathy_chat_get_tp_chat (chat) == NULL) {
841 /* No TpChat, we are disconnected */
842 icon_name = NULL;
844 else if (empathy_chat_get_nb_unread_messages (chat) > 0) {
845 icon_name = EMPATHY_IMAGE_MESSAGE;
847 else if (remote_contact && empathy_chat_is_composing (chat)) {
848 icon_name = EMPATHY_IMAGE_TYPING;
850 else if (empathy_chat_is_sms_channel (chat)) {
851 icon_name = EMPATHY_IMAGE_SMS;
853 else if (remote_contact) {
854 icon_name = empathy_icon_name_for_contact (remote_contact);
855 } else {
856 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
859 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
860 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
861 if (icon_name != NULL) {
862 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
863 gtk_widget_show (tab_image);
864 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
865 gtk_widget_show (menu_image);
866 } else {
867 gtk_widget_hide (tab_image);
868 gtk_widget_hide (menu_image);
871 /* Update the sending spinner */
872 nb_sending = empathy_chat_get_n_messages_sending (chat);
873 sending_spinner = g_object_get_data (G_OBJECT (chat),
874 "chat-window-tab-sending-spinner");
876 g_object_set (sending_spinner,
877 "active", nb_sending > 0,
878 "visible", nb_sending > 0,
879 NULL);
881 /* Update tab tooltip */
882 tooltip = g_string_new (NULL);
884 if (remote_contact) {
885 id = empathy_contact_get_id (remote_contact);
886 status = empathy_contact_get_presence_message (remote_contact);
887 } else {
888 id = name;
891 if (empathy_chat_is_sms_channel (chat)) {
892 append_markup_printf (tooltip, "%s ", _("SMS:"));
895 append_markup_printf (tooltip,
896 "<b>%s</b><small> (%s)</small>",
898 tp_account_get_display_name (account));
900 if (nb_sending > 0) {
901 char *tmp = g_strdup_printf (
902 ngettext ("Sending %d message",
903 "Sending %d messages",
904 nb_sending),
905 nb_sending);
907 g_string_append (tooltip, "\n");
908 g_string_append (tooltip, tmp);
910 gtk_widget_set_tooltip_text (sending_spinner, tmp);
911 g_free (tmp);
914 if (!EMP_STR_EMPTY (status)) {
915 append_markup_printf (tooltip, "\n<i>%s</i>", status);
918 if (subject) {
919 append_markup_printf (tooltip, "\n<b>%s</b> %s",
920 _("Topic:"), subject);
923 if (remote_contact && empathy_chat_is_composing (chat)) {
924 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
927 if (remote_contact != NULL) {
928 const gchar * const *types;
930 types = empathy_contact_get_client_types (remote_contact);
931 if (types != NULL && !tp_strdiff (types[0], "phone")) {
932 /* I'm on a phone ! */
933 gchar *tmp = name;
935 name = g_strdup_printf ("☎ %s", name);
936 g_free (tmp);
940 markup = g_string_free (tooltip, FALSE);
941 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
942 gtk_widget_set_tooltip_markup (widget, markup);
943 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
944 gtk_widget_set_tooltip_markup (widget, markup);
945 g_free (markup);
947 /* Update tab and menu label */
948 if (empathy_chat_is_highlighted (chat)) {
949 markup = g_markup_printf_escaped (
950 "<span color=\"red\" weight=\"bold\">%s</span>",
951 name);
952 } else {
953 markup = g_markup_escape_text (name, -1);
956 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
957 gtk_label_set_markup (GTK_LABEL (widget), markup);
958 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
959 gtk_label_set_markup (GTK_LABEL (widget), markup);
960 g_free (markup);
962 /* Update the window if it's the current chat */
963 if (priv->current_chat == chat) {
964 chat_window_update (window, update_contact_menu);
967 g_free (name);
970 static void
971 chat_window_update_chat_tab (EmpathyChat *chat)
973 chat_window_update_chat_tab_full (chat, TRUE);
976 static void
977 chat_window_chat_notify_cb (EmpathyChat *chat)
979 EmpathyChatWindow *window;
980 EmpathyContact *old_remote_contact;
981 EmpathyContact *remote_contact = NULL;
983 old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
984 remote_contact = empathy_chat_get_remote_contact (chat);
986 if (old_remote_contact != remote_contact) {
987 /* The remote-contact associated with the chat changed, we need
988 * to keep track of any change of that contact and update the
989 * window each time. */
990 if (remote_contact) {
991 g_signal_connect_swapped (remote_contact, "notify",
992 G_CALLBACK (chat_window_update_chat_tab),
993 chat);
995 if (old_remote_contact) {
996 g_signal_handlers_disconnect_by_func (old_remote_contact,
997 chat_window_update_chat_tab,
998 chat);
1001 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1002 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1005 chat_window_update_chat_tab (chat);
1007 window = chat_window_find_chat (chat);
1008 if (window != NULL) {
1009 chat_window_update (window, FALSE);
1013 static void
1014 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1015 EmpathySmiley *smiley,
1016 gpointer window)
1018 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1019 EmpathyChat *chat;
1020 GtkTextBuffer *buffer;
1021 GtkTextIter iter;
1023 chat = priv->current_chat;
1025 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1026 gtk_text_buffer_get_end_iter (buffer, &iter);
1027 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1030 static void
1031 chat_window_conv_activate_cb (GtkAction *action,
1032 EmpathyChatWindow *window)
1034 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1035 gboolean is_room;
1036 gboolean active;
1037 EmpathyContact *remote_contact = NULL;
1039 /* Favorite room menu */
1040 is_room = empathy_chat_is_room (priv->current_chat);
1041 if (is_room) {
1042 const gchar *room;
1043 TpAccount *account;
1044 gboolean found = FALSE;
1045 EmpathyChatroom *chatroom;
1047 room = empathy_chat_get_id (priv->current_chat);
1048 account = empathy_chat_get_account (priv->current_chat);
1049 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1050 account, room);
1051 if (chatroom != NULL)
1052 found = empathy_chatroom_is_favorite (chatroom);
1054 DEBUG ("This room %s favorite", found ? "is" : "is not");
1055 gtk_toggle_action_set_active (
1056 GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1058 if (chatroom != NULL)
1059 found = empathy_chatroom_is_always_urgent (chatroom);
1061 gtk_toggle_action_set_active (
1062 GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1063 found);
1065 gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1066 gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1068 /* Show contacts menu */
1069 g_object_get (priv->current_chat,
1070 "remote-contact", &remote_contact,
1071 "show-contacts", &active,
1072 NULL);
1073 if (remote_contact == NULL) {
1074 gtk_toggle_action_set_active (
1075 GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1076 active);
1078 gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1079 (remote_contact == NULL));
1080 if (remote_contact != NULL) {
1081 g_object_unref (remote_contact);
1085 static void
1086 chat_window_clear_activate_cb (GtkAction *action,
1087 EmpathyChatWindow *window)
1089 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1091 empathy_chat_clear (priv->current_chat);
1094 static void
1095 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1096 EmpathyChatWindow *window)
1098 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1099 gboolean active;
1100 TpAccount *account;
1101 gchar *name;
1102 const gchar *room;
1103 EmpathyChatroom *chatroom;
1105 active = gtk_toggle_action_get_active (toggle_action);
1106 account = empathy_chat_get_account (priv->current_chat);
1107 room = empathy_chat_get_id (priv->current_chat);
1108 name = empathy_chat_dup_name (priv->current_chat);
1110 chatroom = empathy_chatroom_manager_ensure_chatroom (
1111 priv->chatroom_manager,
1112 account,
1113 room,
1114 name);
1116 empathy_chatroom_set_favorite (chatroom, active);
1117 g_object_unref (chatroom);
1118 g_free (name);
1121 static void
1122 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1123 EmpathyChatWindow *window)
1125 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1126 gboolean active;
1127 TpAccount *account;
1128 gchar *name;
1129 const gchar *room;
1130 EmpathyChatroom *chatroom;
1132 active = gtk_toggle_action_get_active (toggle_action);
1133 account = empathy_chat_get_account (priv->current_chat);
1134 room = empathy_chat_get_id (priv->current_chat);
1135 name = empathy_chat_dup_name (priv->current_chat);
1137 chatroom = empathy_chatroom_manager_ensure_chatroom (
1138 priv->chatroom_manager,
1139 account,
1140 room,
1141 name);
1143 empathy_chatroom_set_always_urgent (chatroom, active);
1144 g_object_unref (chatroom);
1145 g_free (name);
1148 static void
1149 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1150 EmpathyChatWindow *window)
1152 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1153 gboolean active;
1155 active = gtk_toggle_action_get_active (toggle_action);
1157 empathy_chat_set_show_contacts (priv->current_chat, active);
1160 static void
1161 chat_window_invite_participant_activate_cb (GtkAction *action,
1162 EmpathyChatWindow *window)
1164 EmpathyChatWindowPriv *priv;
1165 GtkWidget *dialog;
1166 EmpathyTpChat *tp_chat;
1167 int response;
1169 priv = GET_PRIV (window);
1171 g_return_if_fail (priv->current_chat != NULL);
1173 tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1175 dialog = empathy_invite_participant_dialog_new (
1176 GTK_WINDOW (priv->dialog), tp_chat);
1177 gtk_widget_show (dialog);
1179 response = gtk_dialog_run (GTK_DIALOG (dialog));
1181 if (response == GTK_RESPONSE_ACCEPT) {
1182 TpContact *tp_contact;
1183 EmpathyContact *contact;
1185 tp_contact = empathy_invite_participant_dialog_get_selected (
1186 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1187 if (tp_contact == NULL) goto out;
1189 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1191 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1192 contact, _("Inviting you to this room"));
1194 g_object_unref (contact);
1197 out:
1198 gtk_widget_destroy (dialog);
1201 static void
1202 chat_window_close_activate_cb (GtkAction *action,
1203 EmpathyChatWindow *window)
1205 EmpathyChatWindowPriv *priv;
1207 priv = GET_PRIV (window);
1209 g_return_if_fail (priv->current_chat != NULL);
1211 maybe_close_chat (window, priv->current_chat);
1214 static void
1215 chat_window_edit_activate_cb (GtkAction *action,
1216 EmpathyChatWindow *window)
1218 EmpathyChatWindowPriv *priv;
1219 GtkClipboard *clipboard;
1220 GtkTextBuffer *buffer;
1221 gboolean text_available;
1223 priv = GET_PRIV (window);
1225 g_return_if_fail (priv->current_chat != NULL);
1227 if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1228 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1229 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1230 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1231 return;
1234 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1235 if (gtk_text_buffer_get_has_selection (buffer)) {
1236 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1237 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1238 } else {
1239 gboolean selection;
1241 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1243 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1244 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1247 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1248 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1249 gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1252 static void
1253 chat_window_cut_activate_cb (GtkAction *action,
1254 EmpathyChatWindow *window)
1256 EmpathyChatWindowPriv *priv;
1258 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1260 priv = GET_PRIV (window);
1262 empathy_chat_cut (priv->current_chat);
1265 static void
1266 chat_window_copy_activate_cb (GtkAction *action,
1267 EmpathyChatWindow *window)
1269 EmpathyChatWindowPriv *priv;
1271 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1273 priv = GET_PRIV (window);
1275 empathy_chat_copy (priv->current_chat);
1278 static void
1279 chat_window_paste_activate_cb (GtkAction *action,
1280 EmpathyChatWindow *window)
1282 EmpathyChatWindowPriv *priv;
1284 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1286 priv = GET_PRIV (window);
1288 empathy_chat_paste (priv->current_chat);
1291 static void
1292 chat_window_find_activate_cb (GtkAction *action,
1293 EmpathyChatWindow *window)
1295 EmpathyChatWindowPriv *priv;
1297 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1299 priv = GET_PRIV (window);
1301 empathy_chat_find (priv->current_chat);
1304 static void
1305 chat_window_tabs_next_activate_cb (GtkAction *action,
1306 EmpathyChatWindow *window)
1308 EmpathyChatWindowPriv *priv;
1309 gint index_, numPages;
1310 gboolean wrap_around;
1312 priv = GET_PRIV (window);
1314 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1315 &wrap_around, NULL);
1317 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1318 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1320 if (index_ == (numPages - 1) && wrap_around) {
1321 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1322 return;
1325 gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1328 static void
1329 chat_window_tabs_previous_activate_cb (GtkAction *action,
1330 EmpathyChatWindow *window)
1332 EmpathyChatWindowPriv *priv;
1333 gint index_, numPages;
1334 gboolean wrap_around;
1336 priv = GET_PRIV (window);
1338 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1339 &wrap_around, NULL);
1341 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1342 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1344 if (index_ <= 0 && wrap_around) {
1345 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1346 return;
1349 gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1352 static void
1353 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1354 EmpathyChatWindow *window)
1356 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1357 empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1358 empathy_get_current_action_time ());
1361 static void
1362 chat_window_tabs_left_activate_cb (GtkAction *action,
1363 EmpathyChatWindow *window)
1365 EmpathyChatWindowPriv *priv;
1366 EmpathyChat *chat;
1367 gint index_, num_pages;
1369 priv = GET_PRIV (window);
1371 chat = priv->current_chat;
1372 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1373 if (index_ <= 0) {
1374 return;
1377 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1378 GTK_WIDGET (chat),
1379 index_ - 1);
1381 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1382 chat_window_menu_context_update (priv, num_pages);
1385 static void
1386 chat_window_tabs_right_activate_cb (GtkAction *action,
1387 EmpathyChatWindow *window)
1389 EmpathyChatWindowPriv *priv;
1390 EmpathyChat *chat;
1391 gint index_, num_pages;
1393 priv = GET_PRIV (window);
1395 chat = priv->current_chat;
1396 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1398 gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1399 GTK_WIDGET (chat),
1400 index_ + 1);
1402 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1403 chat_window_menu_context_update (priv, num_pages);
1406 static EmpathyChatWindow *
1407 empathy_chat_window_new (void)
1409 return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1412 static void
1413 chat_window_detach_activate_cb (GtkAction *action,
1414 EmpathyChatWindow *window)
1416 EmpathyChatWindowPriv *priv;
1417 EmpathyChatWindow *new_window;
1418 EmpathyChat *chat;
1420 priv = GET_PRIV (window);
1422 chat = priv->current_chat;
1423 new_window = empathy_chat_window_new ();
1425 empathy_chat_window_move_chat (window, new_window, chat);
1427 priv = GET_PRIV (new_window);
1428 gtk_widget_show (priv->dialog);
1431 static void
1432 chat_window_help_contents_activate_cb (GtkAction *action,
1433 EmpathyChatWindow *window)
1435 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1437 empathy_url_show (priv->dialog, "help:empathy");
1440 static void
1441 chat_window_help_about_activate_cb (GtkAction *action,
1442 EmpathyChatWindow *window)
1444 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1446 empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1449 static gboolean
1450 chat_window_delete_event_cb (GtkWidget *dialog,
1451 GdkEvent *event,
1452 EmpathyChatWindow *window)
1454 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1455 EmpathyChat *chat = NULL;
1456 guint n_rooms = 0;
1457 GList *l;
1459 DEBUG ("Delete event received");
1461 for (l = priv->chats; l != NULL; l = l->next) {
1462 if (chat_needs_close_confirmation (l->data)) {
1463 chat = l->data;
1464 n_rooms++;
1468 if (n_rooms > 0) {
1469 confirm_close (window, TRUE, n_rooms,
1470 (n_rooms == 1 ? chat : NULL));
1471 } else {
1472 remove_all_chats (window);
1475 return TRUE;
1478 static void
1479 chat_window_composing_cb (EmpathyChat *chat,
1480 gboolean is_composing,
1481 EmpathyChatWindow *window)
1483 chat_window_update_chat_tab (chat);
1486 static void
1487 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1488 gboolean urgent)
1490 EmpathyChatWindowPriv *priv;
1492 priv = GET_PRIV (window);
1494 gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1497 static void
1498 chat_window_notification_closed_cb (NotifyNotification *notify,
1499 EmpathyChatWindow *self)
1501 EmpathyChatWindowPriv *priv = GET_PRIV (self);
1503 g_object_unref (notify);
1504 if (priv->notification == notify) {
1505 priv->notification = NULL;
1509 static void
1510 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1511 EmpathyMessage *message,
1512 EmpathyChat *chat)
1514 EmpathyContact *sender;
1515 const gchar *header;
1516 char *escaped;
1517 const char *body;
1518 GdkPixbuf *pixbuf;
1519 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1520 gboolean res, has_x_canonical_append;
1521 NotifyNotification *notification = priv->notification;
1523 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1524 return;
1525 } else {
1526 res = g_settings_get_boolean (priv->gsettings_notif,
1527 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1529 if (!res) {
1530 return;
1534 sender = empathy_message_get_sender (message);
1535 header = empathy_contact_get_alias (sender);
1536 body = empathy_message_get_body (message);
1537 escaped = g_markup_escape_text (body, -1);
1538 has_x_canonical_append = empathy_notify_manager_has_capability (
1539 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1541 if (notification != NULL && !has_x_canonical_append) {
1542 /* if the notification server supports x-canonical-append, it is
1543 better to not use notify_notification_update to avoid
1544 overwriting the current notification message */
1545 notify_notification_update (notification,
1546 header, escaped, NULL);
1547 } else {
1548 /* if the notification server supports x-canonical-append,
1549 the hint will be added, so that the message from the
1550 just created notification will be automatically appended
1551 to an existing notification with the same title.
1552 In this way the previous message will not be lost: the new
1553 message will appear below it, in the same notification */
1554 notification = notify_notification_new (header, escaped, NULL);
1556 if (priv->notification == NULL) {
1557 priv->notification = notification;
1560 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1562 tp_g_signal_connect_object (notification, "closed",
1563 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1565 if (has_x_canonical_append) {
1566 /* We have to set a not empty string to keep libnotify happy */
1567 notify_notification_set_hint_string (notification,
1568 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1572 const gchar *category = empathy_chat_is_room (chat)
1573 ? "x-empathy.im.mentioned"
1574 : "im.received";
1575 notify_notification_set_hint (notification,
1576 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1577 g_variant_new_string (category));
1581 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1582 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1584 if (pixbuf != NULL) {
1585 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1586 g_object_unref (pixbuf);
1589 notify_notification_show (notification, NULL);
1591 g_free (escaped);
1594 static gboolean
1595 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1597 EmpathyChatWindowPriv *priv;
1598 gboolean has_focus;
1600 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1602 priv = GET_PRIV (window);
1604 g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1606 return has_focus;
1609 static void
1610 chat_window_new_message_cb (EmpathyChat *chat,
1611 EmpathyMessage *message,
1612 gboolean pending,
1613 gboolean should_highlight,
1614 EmpathyChatWindow *window)
1616 EmpathyChatWindowPriv *priv;
1617 gboolean has_focus;
1618 gboolean needs_urgency;
1619 EmpathyContact *sender;
1621 priv = GET_PRIV (window);
1623 has_focus = empathy_chat_window_has_focus (window);
1625 /* - if we're the sender, we play the sound if it's specified in the
1626 * preferences and we're not away.
1627 * - if we receive a message, we play the sound if it's specified in the
1628 * preferences and the window does not have focus on the chat receiving
1629 * the message.
1632 sender = empathy_message_get_sender (message);
1634 if (empathy_contact_is_user (sender)) {
1635 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1636 EMPATHY_SOUND_MESSAGE_OUTGOING);
1639 if (has_focus && priv->current_chat == chat) {
1640 /* window and tab are focused so consider the message to be read */
1642 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1643 empathy_chat_messages_read (chat);
1644 return;
1647 /* Update the chat tab if this is the first unread message */
1648 if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1649 chat_window_update_chat_tab (chat);
1652 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1653 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1654 * an unamed MUC (msn-like).
1655 * In case of a MUC, we set urgency if either:
1656 * a) the chatroom's always_urgent property is TRUE
1657 * b) the message contains our alias
1659 if (empathy_chat_is_room (chat)) {
1660 TpAccount *account;
1661 const gchar *room;
1662 EmpathyChatroom *chatroom;
1664 account = empathy_chat_get_account (chat);
1665 room = empathy_chat_get_id (chat);
1667 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1668 account, room);
1670 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1671 needs_urgency = TRUE;
1672 } else {
1673 needs_urgency = should_highlight;
1675 } else {
1676 needs_urgency = TRUE;
1679 if (needs_urgency) {
1680 if (!has_focus) {
1681 chat_window_set_urgency_hint (window, TRUE);
1684 /* Pending messages have already been displayed and notified in the
1685 * approver, so we don't display a notification and play a sound for those */
1686 if (!pending) {
1687 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1688 EMPATHY_SOUND_MESSAGE_INCOMING);
1690 chat_window_show_or_update_notification (window, message, chat);
1694 /* update the number of unread messages and the window icon */
1695 chat_window_title_update (priv);
1696 chat_window_icon_update (priv, TRUE);
1699 static void
1700 chat_window_command_part (EmpathyChat *chat,
1701 GStrv strv)
1703 EmpathyChat *chat_to_be_parted;
1704 EmpathyTpChat *tp_chat = NULL;
1706 if (strv[1] == NULL) {
1707 /* No chatroom ID specified */
1708 tp_chat = empathy_chat_get_tp_chat (chat);
1709 if (tp_chat)
1710 empathy_tp_chat_leave (tp_chat, "");
1711 return;
1713 chat_to_be_parted = empathy_chat_window_find_chat (
1714 empathy_chat_get_account (chat), strv[1], FALSE);
1716 if (chat_to_be_parted != NULL) {
1717 /* Found a chatroom matching the specified ID */
1718 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1719 if (tp_chat)
1720 empathy_tp_chat_leave (tp_chat, strv[2]);
1721 } else {
1722 gchar *message;
1724 /* Going by the syntax of PART command:
1726 * /PART [<chatroom-ID>] [<reason>]
1728 * Chatroom-ID is not a must to specify a reason.
1729 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1730 * MUC then the current chatroom should be parted and srtv[1] should
1731 * be treated as part of the optional part-message. */
1732 message = g_strconcat (strv[1], " ", strv[2], NULL);
1733 tp_chat = empathy_chat_get_tp_chat (chat);
1734 if (tp_chat)
1735 empathy_tp_chat_leave (tp_chat, message);
1737 g_free (message);
1741 static GtkNotebook *
1742 notebook_create_window_cb (GtkNotebook *source,
1743 GtkWidget *page,
1744 gint x,
1745 gint y,
1746 gpointer user_data)
1748 EmpathyChatWindowPriv *priv;
1749 EmpathyChatWindow *window, *new_window;
1750 EmpathyChat *chat;
1752 chat = EMPATHY_CHAT (page);
1753 window = chat_window_find_chat (chat);
1755 new_window = empathy_chat_window_new ();
1756 priv = GET_PRIV (new_window);
1758 DEBUG ("Detach hook called");
1760 empathy_chat_window_move_chat (window, new_window, chat);
1762 gtk_widget_show (priv->dialog);
1763 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1765 return NULL;
1768 static void
1769 chat_window_page_switched_cb (GtkNotebook *notebook,
1770 GtkWidget *child,
1771 gint page_num,
1772 EmpathyChatWindow *window)
1774 EmpathyChatWindowPriv *priv = GET_PRIV (window);
1775 EmpathyChat *chat = EMPATHY_CHAT (child);
1777 DEBUG ("Page switched");
1779 if (priv->page_added) {
1780 priv->page_added = FALSE;
1781 empathy_chat_scroll_down (chat);
1783 else if (priv->current_chat == chat) {
1784 return;
1787 priv->current_chat = chat;
1788 empathy_chat_messages_read (chat);
1790 chat_window_update_chat_tab (chat);
1793 static void
1794 chat_window_page_added_cb (GtkNotebook *notebook,
1795 GtkWidget *child,
1796 guint page_num,
1797 EmpathyChatWindow *window)
1799 EmpathyChatWindowPriv *priv;
1800 EmpathyChat *chat;
1802 priv = GET_PRIV (window);
1804 /* If we just received DND to the same window, we don't want
1805 * to do anything here like removing the tab and then readding
1806 * it, so we return here and in "page-added".
1808 if (priv->dnd_same_window) {
1809 DEBUG ("Page added (back to the same window)");
1810 priv->dnd_same_window = FALSE;
1811 return;
1814 DEBUG ("Page added");
1816 /* Get chat object */
1817 chat = EMPATHY_CHAT (child);
1819 /* Connect chat signals for this window */
1820 g_signal_connect (chat, "composing",
1821 G_CALLBACK (chat_window_composing_cb),
1822 window);
1823 g_signal_connect (chat, "new-message",
1824 G_CALLBACK (chat_window_new_message_cb),
1825 window);
1826 g_signal_connect (chat, "part-command-entered",
1827 G_CALLBACK (chat_window_command_part),
1828 NULL);
1829 g_signal_connect (chat, "notify::tp-chat",
1830 G_CALLBACK (chat_window_update_chat_tab),
1831 window);
1833 /* Set flag so we know to perform some special operations on
1834 * switch page due to the new page being added.
1836 priv->page_added = TRUE;
1838 /* Get list of chats up to date */
1839 priv->chats = g_list_append (priv->chats, chat);
1841 chat_window_update_chat_tab (chat);
1844 static void
1845 chat_window_page_removed_cb (GtkNotebook *notebook,
1846 GtkWidget *child,
1847 guint page_num,
1848 EmpathyChatWindow *window)
1850 EmpathyChatWindowPriv *priv;
1851 EmpathyChat *chat;
1853 priv = GET_PRIV (window);
1855 /* If we just received DND to the same window, we don't want
1856 * to do anything here like removing the tab and then readding
1857 * it, so we return here and in "page-added".
1859 if (priv->dnd_same_window) {
1860 DEBUG ("Page removed (and will be readded to same window)");
1861 return;
1864 DEBUG ("Page removed");
1866 /* Get chat object */
1867 chat = EMPATHY_CHAT (child);
1869 /* Disconnect all signal handlers for this chat and this window */
1870 g_signal_handlers_disconnect_by_func (chat,
1871 G_CALLBACK (chat_window_composing_cb),
1872 window);
1873 g_signal_handlers_disconnect_by_func (chat,
1874 G_CALLBACK (chat_window_new_message_cb),
1875 window);
1876 g_signal_handlers_disconnect_by_func (chat,
1877 G_CALLBACK (chat_window_update_chat_tab),
1878 window);
1880 /* Keep list of chats up to date */
1881 priv->chats = g_list_remove (priv->chats, chat);
1882 empathy_chat_messages_read (chat);
1884 if (priv->chats == NULL) {
1885 g_object_unref (window);
1886 } else {
1887 chat_window_update (window, TRUE);
1891 static gboolean
1892 chat_window_focus_in_event_cb (GtkWidget *widget,
1893 GdkEvent *event,
1894 EmpathyChatWindow *window)
1896 EmpathyChatWindowPriv *priv;
1898 priv = GET_PRIV (window);
1900 empathy_chat_messages_read (priv->current_chat);
1902 chat_window_set_urgency_hint (window, FALSE);
1904 /* Update the title, since we now mark all unread messages as read. */
1905 chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1907 return FALSE;
1910 static gboolean
1911 chat_window_drag_drop (GtkWidget *widget,
1912 GdkDragContext *context,
1913 int x,
1914 int y,
1915 guint time_,
1916 EmpathyChatWindow *window)
1918 GdkAtom target;
1919 EmpathyChatWindowPriv *priv;
1921 priv = GET_PRIV (window);
1923 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1924 if (target == GDK_NONE)
1925 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1927 if (target != GDK_NONE) {
1928 gtk_drag_get_data (widget, context, target, time_);
1929 return TRUE;
1932 return FALSE;
1935 static gboolean
1936 chat_window_drag_motion (GtkWidget *widget,
1937 GdkDragContext *context,
1938 int x,
1939 int y,
1940 guint time_,
1941 EmpathyChatWindow *window)
1943 GdkAtom target;
1944 EmpathyChatWindowPriv *priv;
1946 priv = GET_PRIV (window);
1948 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1949 if (target != GDK_NONE) {
1950 /* This is a file drag. Ensure the contact is online and set the
1951 drag type to COPY. Note that it's possible that the tab will
1952 be switched by GTK+ after a timeout from drag_motion without
1953 getting another drag_motion to disable the drop. You have
1954 to hold your mouse really still.
1956 EmpathyContact *contact;
1958 priv = GET_PRIV (window);
1959 contact = empathy_chat_get_remote_contact (priv->current_chat);
1960 /* contact is NULL for multi-user chats. We don't do
1961 * file transfers to MUCs. We also don't send files
1962 * to offline contacts or contacts that don't support
1963 * file transfer.
1965 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1966 gdk_drag_status (context, 0, time_);
1967 return FALSE;
1969 if (!(empathy_contact_get_capabilities (contact)
1970 & EMPATHY_CAPABILITIES_FT)) {
1971 gdk_drag_status (context, 0, time_);
1972 return FALSE;
1974 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1975 return TRUE;
1978 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1979 if (target != GDK_NONE) {
1980 /* This is a drag of a contact from a contact list. Set to COPY.
1981 FIXME: If this drag is to a MUC window, it invites the user.
1982 Otherwise, it opens a chat. Should we use a different drag
1983 type for invites? Should we allow ASK?
1985 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1986 return TRUE;
1989 return FALSE;
1992 static void
1993 drag_data_received_individual_id (EmpathyChatWindow *self,
1994 GtkWidget *widget,
1995 GdkDragContext *context,
1996 int x,
1997 int y,
1998 GtkSelectionData *selection,
1999 guint info,
2000 guint time_)
2002 const gchar *id;
2003 EmpathyIndividualManager *manager = NULL;
2004 FolksIndividual *individual;
2005 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2006 EmpathyTpChat *chat;
2007 TpContact *tp_contact;
2008 TpConnection *conn;
2009 EmpathyContact *contact;
2011 id = (const gchar *) gtk_selection_data_get_data (selection);
2013 DEBUG ("DND invididual %s", id);
2015 if (priv->current_chat == NULL)
2016 goto out;
2018 chat = empathy_chat_get_tp_chat (priv->current_chat);
2019 if (chat == NULL)
2020 goto out;
2022 if (!empathy_tp_chat_can_add_contact (chat)) {
2023 DEBUG ("Can't invite contact to %s",
2024 tp_proxy_get_object_path (chat));
2025 goto out;
2028 manager = empathy_individual_manager_dup_singleton ();
2030 individual = empathy_individual_manager_lookup_member (manager, id);
2031 if (individual == NULL) {
2032 DEBUG ("Failed to find individual %s", id);
2033 goto out;
2036 conn = tp_channel_borrow_connection ((TpChannel *) chat);
2037 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2038 if (tp_contact == NULL) {
2039 DEBUG ("Can't find a TpContact on connection %s for %s",
2040 tp_proxy_get_object_path (conn), id);
2041 goto out;
2044 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2045 tp_channel_get_identifier ((TpChannel *) chat));
2047 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2048 empathy_contact_list_add (EMPATHY_CONTACT_LIST (chat), contact, NULL);
2049 g_object_unref (contact);
2051 out:
2052 gtk_drag_finish (context, TRUE, FALSE, time_);
2053 tp_clear_object (&manager);
2056 static void
2057 chat_window_drag_data_received (GtkWidget *widget,
2058 GdkDragContext *context,
2059 int x,
2060 int y,
2061 GtkSelectionData *selection,
2062 guint info,
2063 guint time_,
2064 EmpathyChatWindow *window)
2066 if (info == DND_DRAG_TYPE_CONTACT_ID) {
2067 EmpathyChat *chat = NULL;
2068 EmpathyChatWindow *old_window;
2069 TpAccount *account = NULL;
2070 EmpathyClientFactory *factory;
2071 const gchar *id;
2072 gchar **strv;
2073 const gchar *account_id;
2074 const gchar *contact_id;
2076 id = (const gchar*) gtk_selection_data_get_data (selection);
2078 factory = empathy_client_factory_dup ();
2080 DEBUG ("DND contact from roster with id:'%s'", id);
2082 strv = g_strsplit (id, ":", 2);
2083 if (g_strv_length (strv) == 2) {
2084 account_id = strv[0];
2085 contact_id = strv[1];
2086 account =
2087 tp_simple_client_factory_ensure_account (
2088 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2089 NULL, NULL);
2091 g_object_unref (factory);
2092 if (account != NULL)
2093 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2096 if (account == NULL) {
2097 g_strfreev (strv);
2098 gtk_drag_finish (context, FALSE, FALSE, time_);
2099 return;
2102 if (!chat) {
2103 empathy_chat_with_contact_id (
2104 account, contact_id,
2105 empathy_get_current_action_time (),
2106 NULL, NULL);
2108 g_strfreev (strv);
2109 return;
2111 g_strfreev (strv);
2113 old_window = chat_window_find_chat (chat);
2114 if (old_window) {
2115 if (old_window == window) {
2116 gtk_drag_finish (context, TRUE, FALSE, time_);
2117 return;
2120 empathy_chat_window_move_chat (old_window, window, chat);
2121 } else {
2122 empathy_chat_window_add_chat (window, chat);
2125 /* Added to take care of any outstanding chat events */
2126 empathy_chat_window_present_chat (chat,
2127 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2129 /* We should return TRUE to remove the data when doing
2130 * GDK_ACTION_MOVE, but we don't here otherwise it has
2131 * weird consequences, and we handle that internally
2132 * anyway with add_chat () and remove_chat ().
2134 gtk_drag_finish (context, TRUE, FALSE, time_);
2136 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2137 drag_data_received_individual_id (window, widget, context, x, y,
2138 selection, info, time_);
2140 else if (info == DND_DRAG_TYPE_URI_LIST) {
2141 EmpathyChatWindowPriv *priv;
2142 EmpathyContact *contact;
2143 const gchar *data;
2145 priv = GET_PRIV (window);
2146 contact = empathy_chat_get_remote_contact (priv->current_chat);
2148 /* contact is NULL when current_chat is a multi-user chat.
2149 * We don't do file transfers to MUCs, so just cancel the drag.
2151 if (contact == NULL) {
2152 gtk_drag_finish (context, TRUE, FALSE, time_);
2153 return;
2156 data = (const gchar *) gtk_selection_data_get_data (selection);
2157 empathy_send_file_from_uri_list (contact, data);
2159 gtk_drag_finish (context, TRUE, FALSE, time_);
2161 else if (info == DND_DRAG_TYPE_TAB) {
2162 EmpathyChat **chat;
2163 EmpathyChatWindow *old_window = NULL;
2165 DEBUG ("DND tab");
2167 chat = (void *) gtk_selection_data_get_data (selection);
2168 old_window = chat_window_find_chat (*chat);
2170 if (old_window) {
2171 EmpathyChatWindowPriv *priv;
2173 priv = GET_PRIV (window);
2174 priv->dnd_same_window = (old_window == window);
2175 DEBUG ("DND tab (within same window: %s)",
2176 priv->dnd_same_window ? "Yes" : "No");
2178 } else {
2179 DEBUG ("DND from unknown source");
2180 gtk_drag_finish (context, FALSE, FALSE, time_);
2184 static void
2185 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2186 guint num_chats_in_manager,
2187 EmpathyChatWindow *window)
2189 EmpathyChatWindowPriv *priv = GET_PRIV (window);
2191 gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2192 num_chats_in_manager > 0);
2195 static void
2196 chat_window_finalize (GObject *object)
2198 EmpathyChatWindow *window;
2199 EmpathyChatWindowPriv *priv;
2201 window = EMPATHY_CHAT_WINDOW (object);
2202 priv = GET_PRIV (window);
2204 DEBUG ("Finalized: %p", object);
2206 g_object_unref (priv->ui_manager);
2207 g_object_unref (priv->chatroom_manager);
2208 g_object_unref (priv->notify_mgr);
2209 g_object_unref (priv->gsettings_chat);
2210 g_object_unref (priv->gsettings_notif);
2211 g_object_unref (priv->gsettings_ui);
2212 g_object_unref (priv->sound_mgr);
2214 if (priv->notification != NULL) {
2215 notify_notification_close (priv->notification, NULL);
2216 priv->notification = NULL;
2219 if (priv->contact_targets) {
2220 gtk_target_list_unref (priv->contact_targets);
2222 if (priv->file_targets) {
2223 gtk_target_list_unref (priv->file_targets);
2226 if (priv->chat_manager) {
2227 g_signal_handler_disconnect (priv->chat_manager,
2228 priv->chat_manager_chats_changed_id);
2229 g_object_unref (priv->chat_manager);
2230 priv->chat_manager = NULL;
2233 chat_windows = g_list_remove (chat_windows, window);
2234 gtk_widget_destroy (priv->dialog);
2236 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2239 static void
2240 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2242 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2244 object_class->finalize = chat_window_finalize;
2246 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2249 static void
2250 empathy_chat_window_init (EmpathyChatWindow *window)
2252 GtkBuilder *gui;
2253 GtkAccelGroup *accel_group;
2254 GClosure *closure;
2255 GtkWidget *menu;
2256 GtkWidget *submenu;
2257 guint i;
2258 GtkWidget *chat_vbox;
2259 gchar *filename;
2260 EmpathySmileyManager *smiley_manager;
2261 EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2262 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2264 window->priv = priv;
2265 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2266 gui = empathy_builder_get_file (filename,
2267 "chat_window", &priv->dialog,
2268 "chat_vbox", &chat_vbox,
2269 "ui_manager", &priv->ui_manager,
2270 "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2271 "menu_conv_favorite", &priv->menu_conv_favorite,
2272 "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2273 "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2274 "menu_edit_cut", &priv->menu_edit_cut,
2275 "menu_edit_copy", &priv->menu_edit_copy,
2276 "menu_edit_paste", &priv->menu_edit_paste,
2277 "menu_edit_find", &priv->menu_edit_find,
2278 "menu_tabs_next", &priv->menu_tabs_next,
2279 "menu_tabs_prev", &priv->menu_tabs_prev,
2280 "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2281 "menu_tabs_left", &priv->menu_tabs_left,
2282 "menu_tabs_right", &priv->menu_tabs_right,
2283 "menu_tabs_detach", &priv->menu_tabs_detach,
2284 NULL);
2285 g_free (filename);
2287 empathy_builder_connect (gui, window,
2288 "menu_conv", "activate", chat_window_conv_activate_cb,
2289 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2290 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2291 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2292 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2293 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2294 "menu_conv_close", "activate", chat_window_close_activate_cb,
2295 "menu_edit", "activate", chat_window_edit_activate_cb,
2296 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2297 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2298 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2299 "menu_edit_find", "activate", chat_window_find_activate_cb,
2300 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2301 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2302 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2303 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2304 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2305 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2306 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2307 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2308 NULL);
2310 g_object_ref (priv->ui_manager);
2311 g_object_unref (gui);
2313 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2314 priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2315 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2316 priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2318 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2320 priv->notebook = gtk_notebook_new ();
2322 g_signal_connect (priv->notebook, "create-window",
2323 G_CALLBACK (notebook_create_window_cb), window);
2325 gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2326 "EmpathyChatWindow");
2327 gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2328 gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2329 gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2330 gtk_widget_show (priv->notebook);
2332 /* Set up accels */
2333 accel_group = gtk_accel_group_new ();
2334 gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2336 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2337 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2338 window,
2339 NULL);
2340 gtk_accel_group_connect (accel_group,
2341 tab_accel_keys[i],
2342 GDK_MOD1_MASK,
2344 closure);
2347 g_object_unref (accel_group);
2349 /* Set up drag target lists */
2350 priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2351 G_N_ELEMENTS (drag_types_dest_contact));
2352 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2353 G_N_ELEMENTS (drag_types_dest_file));
2355 /* Set up smiley menu */
2356 smiley_manager = empathy_smiley_manager_dup_singleton ();
2357 submenu = empathy_smiley_menu_new (smiley_manager,
2358 chat_window_insert_smiley_activate_cb,
2359 window);
2360 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2361 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2362 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2363 g_object_unref (smiley_manager);
2365 /* Set up signals we can't do with ui file since we may need to
2366 * block/unblock them at some later stage.
2369 g_signal_connect (priv->dialog,
2370 "delete_event",
2371 G_CALLBACK (chat_window_delete_event_cb),
2372 window);
2373 g_signal_connect (priv->dialog,
2374 "focus_in_event",
2375 G_CALLBACK (chat_window_focus_in_event_cb),
2376 window);
2377 g_signal_connect_after (priv->notebook,
2378 "switch_page",
2379 G_CALLBACK (chat_window_page_switched_cb),
2380 window);
2381 g_signal_connect (priv->notebook,
2382 "page_added",
2383 G_CALLBACK (chat_window_page_added_cb),
2384 window);
2385 g_signal_connect (priv->notebook,
2386 "page_removed",
2387 G_CALLBACK (chat_window_page_removed_cb),
2388 window);
2390 /* Set up drag and drop */
2391 gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2392 GTK_DEST_DEFAULT_HIGHLIGHT,
2393 drag_types_dest,
2394 G_N_ELEMENTS (drag_types_dest),
2395 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2397 /* connect_after to allow GtkNotebook's built-in tab switching */
2398 g_signal_connect_after (priv->notebook,
2399 "drag-motion",
2400 G_CALLBACK (chat_window_drag_motion),
2401 window);
2402 g_signal_connect (priv->notebook,
2403 "drag-data-received",
2404 G_CALLBACK (chat_window_drag_data_received),
2405 window);
2406 g_signal_connect (priv->notebook,
2407 "drag-drop",
2408 G_CALLBACK (chat_window_drag_drop),
2409 window);
2411 chat_windows = g_list_prepend (chat_windows, window);
2413 /* Set up private details */
2414 priv->chats = NULL;
2415 priv->current_chat = NULL;
2416 priv->notification = NULL;
2418 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2420 priv->chat_manager = empathy_chat_manager_dup_singleton ();
2421 priv->chat_manager_chats_changed_id =
2422 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2423 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2424 window);
2426 chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2427 empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2428 window);
2431 /* Returns the window to open a new tab in if there is a suitable window,
2432 * otherwise, returns NULL indicating that a new window should be added.
2434 static EmpathyChatWindow *
2435 empathy_chat_window_get_default (gboolean room)
2437 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2438 GList *l;
2439 gboolean separate_windows = TRUE;
2441 separate_windows = g_settings_get_boolean (gsettings,
2442 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2444 g_object_unref (gsettings);
2446 if (separate_windows) {
2447 /* Always create a new window */
2448 return NULL;
2451 for (l = chat_windows; l; l = l->next) {
2452 EmpathyChatWindow *chat_window;
2453 guint nb_rooms, nb_private;
2455 chat_window = l->data;
2457 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2459 /* Skip the window if there aren't any rooms in it */
2460 if (room && nb_rooms == 0)
2461 continue;
2463 /* Skip the window if there aren't any 1-1 chats in it */
2464 if (!room && nb_private == 0)
2465 continue;
2467 return chat_window;
2470 return NULL;
2473 static void
2474 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2475 EmpathyChat *chat)
2477 EmpathyChatWindowPriv *priv;
2478 GtkWidget *label;
2479 GtkWidget *popup_label;
2480 GtkWidget *child;
2481 GValue value = { 0, };
2483 g_return_if_fail (window != NULL);
2484 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2486 priv = GET_PRIV (window);
2488 /* Reference the chat object */
2489 g_object_ref (chat);
2491 /* If this window has just been created, position it */
2492 if (priv->chats == NULL) {
2493 const gchar *name = "chat-window";
2494 gboolean separate_windows;
2496 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2497 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2499 if (empathy_chat_is_room (chat))
2500 name = "room-window";
2502 if (separate_windows) {
2503 gint x, y;
2505 /* Save current position of the window */
2506 gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2508 /* First bind to the 'generic' name. So new window for which we didn't
2509 * save a geometry yet will have the geometry of the last saved
2510 * window (bgo #601191). */
2511 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2513 /* Restore previous position of the window so the newly created window
2514 * won't be in the same position as the latest saved window and so
2515 * completely hide it. */
2516 gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2518 /* Then bind it to the name of the contact/room so we'll save the
2519 * geometry specific to this window */
2520 name = empathy_chat_get_id (chat);
2523 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2526 child = GTK_WIDGET (chat);
2527 label = chat_window_create_label (window, chat, TRUE);
2528 popup_label = chat_window_create_label (window, chat, FALSE);
2529 gtk_widget_show (child);
2531 g_signal_connect (chat, "notify::name",
2532 G_CALLBACK (chat_window_chat_notify_cb),
2533 NULL);
2534 g_signal_connect (chat, "notify::subject",
2535 G_CALLBACK (chat_window_chat_notify_cb),
2536 NULL);
2537 g_signal_connect (chat, "notify::remote-contact",
2538 G_CALLBACK (chat_window_chat_notify_cb),
2539 NULL);
2540 g_signal_connect (chat, "notify::sms-channel",
2541 G_CALLBACK (chat_window_chat_notify_cb),
2542 NULL);
2543 g_signal_connect (chat, "notify::n-messages-sending",
2544 G_CALLBACK (chat_window_chat_notify_cb),
2545 NULL);
2546 g_signal_connect (chat, "notify::nb-unread-messages",
2547 G_CALLBACK (chat_window_chat_notify_cb),
2548 NULL);
2549 chat_window_chat_notify_cb (chat);
2551 gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2552 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2553 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2554 g_value_init (&value, G_TYPE_BOOLEAN);
2555 g_value_set_boolean (&value, TRUE);
2556 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2557 child, "tab-expand" , &value);
2558 gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2559 child, "tab-fill" , &value);
2560 g_value_unset (&value);
2562 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2565 static void
2566 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2567 EmpathyChat *chat)
2569 EmpathyChatWindowPriv *priv;
2570 gint position;
2571 EmpathyContact *remote_contact;
2572 EmpathyChatManager *chat_manager;
2574 g_return_if_fail (window != NULL);
2575 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2577 priv = GET_PRIV (window);
2579 g_signal_handlers_disconnect_by_func (chat,
2580 chat_window_chat_notify_cb,
2581 NULL);
2582 remote_contact = g_object_get_data (G_OBJECT (chat),
2583 "chat-window-remote-contact");
2584 if (remote_contact) {
2585 g_signal_handlers_disconnect_by_func (remote_contact,
2586 chat_window_update_chat_tab,
2587 chat);
2590 chat_manager = empathy_chat_manager_dup_singleton ();
2591 empathy_chat_manager_closed_chat (chat_manager, chat);
2592 g_object_unref (chat_manager);
2594 position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2595 GTK_WIDGET (chat));
2596 gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2598 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2600 g_object_unref (chat);
2603 static void
2604 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2605 EmpathyChatWindow *new_window,
2606 EmpathyChat *chat)
2608 GtkWidget *widget;
2610 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2611 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2612 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2614 widget = GTK_WIDGET (chat);
2616 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2617 G_OBJECT (widget)->ref_count);
2619 /* We reference here to make sure we don't loose the widget
2620 * and the EmpathyChat object during the move.
2622 g_object_ref (chat);
2623 g_object_ref (widget);
2625 empathy_chat_window_remove_chat (old_window, chat);
2626 empathy_chat_window_add_chat (new_window, chat);
2628 g_object_unref (widget);
2629 g_object_unref (chat);
2632 static void
2633 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2634 EmpathyChat *chat)
2636 EmpathyChatWindowPriv *priv;
2637 gint page_num;
2639 g_return_if_fail (window != NULL);
2640 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2642 priv = GET_PRIV (window);
2644 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2645 GTK_WIDGET (chat));
2646 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2647 page_num);
2650 EmpathyChat *
2651 empathy_chat_window_find_chat (TpAccount *account,
2652 const gchar *id,
2653 gboolean sms_channel)
2655 GList *l;
2657 g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2659 for (l = chat_windows; l; l = l->next) {
2660 EmpathyChatWindowPriv *priv;
2661 EmpathyChatWindow *window;
2662 GList *ll;
2664 window = l->data;
2665 priv = GET_PRIV (window);
2667 for (ll = priv->chats; ll; ll = ll->next) {
2668 EmpathyChat *chat;
2670 chat = ll->data;
2672 if (account == empathy_chat_get_account (chat) &&
2673 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2674 sms_channel == empathy_chat_is_sms_channel (chat)) {
2675 return chat;
2680 return NULL;
2683 void
2684 empathy_chat_window_present_chat (EmpathyChat *chat,
2685 gint64 timestamp)
2687 EmpathyChatWindow *window;
2688 EmpathyChatWindowPriv *priv;
2689 guint32 x_timestamp;
2691 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2693 window = chat_window_find_chat (chat);
2695 /* If the chat has no window, create one */
2696 if (window == NULL) {
2697 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2698 if (!window) {
2699 window = empathy_chat_window_new ();
2701 /* we want to display the newly created window even if we don't present
2702 * it */
2703 priv = GET_PRIV (window);
2704 gtk_widget_show (priv->dialog);
2707 empathy_chat_window_add_chat (window, chat);
2710 /* Don't force the window to show itself when it wasn't
2711 * an action by the user
2713 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2714 return;
2716 priv = GET_PRIV (window);
2718 if (x_timestamp != GDK_CURRENT_TIME) {
2719 /* Don't present or switch tab if the action was earlier than the
2720 * last actions X time, accounting for overflow and the first ever
2721 * presentation */
2723 if (priv->x_user_action_time != 0
2724 && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2725 return;
2727 priv->x_user_action_time = x_timestamp;
2730 empathy_chat_window_switch_to_chat (window, chat);
2732 /* Don't use empathy_window_present_with_time () which would move the window
2733 * to our current desktop but move to the window's desktop instead. This is
2734 * more coherent with Shell's 'app is ready' notication which moves the view
2735 * to the app desktop rather than moving the app itself. */
2736 empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2738 gtk_widget_grab_focus (chat->input_text_view);
2741 static void
2742 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2743 guint *nb_rooms,
2744 guint *nb_private)
2746 EmpathyChatWindowPriv *priv = GET_PRIV (self);
2747 GList *l;
2748 guint _nb_rooms = 0, _nb_private = 0;
2750 for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2751 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2752 _nb_rooms++;
2753 else
2754 _nb_private++;
2757 if (nb_rooms != NULL)
2758 *nb_rooms = _nb_rooms;
2759 if (nb_private != NULL)
2760 *nb_private = _nb_private;