Updated French translation
[empathy-mirror.git] / src / empathy-chat-window.c
blobb496356290bba217485b5ac9de5b63325e074b5c
1 /*
2 * Copyright (C) 2003-2007 Imendio AB
3 * Copyright (C) 2007-2012 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Richard Hult <richard@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24 * Xavier Claessens <xclaesse@gmail.com>
25 * Rômulo Fernandes Machado <romulo@castorgroup.net>
28 #include "config.h"
29 #include "empathy-chat-window.h"
31 #include <glib/gi18n.h>
32 #include <tp-account-widgets/tpaw-builder.h>
33 #include <tp-account-widgets/tpaw-utils.h>
35 #include "empathy-about-dialog.h"
36 #include "empathy-chat-manager.h"
37 #include "empathy-chatroom-manager.h"
38 #include "empathy-client-factory.h"
39 #include "empathy-geometry.h"
40 #include "empathy-gsettings.h"
41 #include "empathy-images.h"
42 #include "empathy-invite-participant-dialog.h"
43 #include "empathy-notify-manager.h"
44 #include "empathy-request-util.h"
45 #include "empathy-sound-manager.h"
46 #include "empathy-ui-utils.h"
47 #include "empathy-utils.h"
49 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
50 #include "empathy-debug.h"
52 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
54 #define X_EARLIER_OR_EQL(t1, t2) \
55 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
56 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
59 enum
61 PROP_INDIVIDUAL_MGR = 1
64 struct _EmpathyChatWindowPriv
66 EmpathyChat *current_chat;
67 GList *chats;
68 gboolean page_added;
69 gboolean dnd_same_window;
70 EmpathyChatroomManager *chatroom_manager;
71 EmpathyNotifyManager *notify_mgr;
72 EmpathyIndividualManager *individual_mgr;
73 GtkWidget *notebook;
74 NotifyNotification *notification;
76 GtkTargetList *contact_targets;
77 GtkTargetList *file_targets;
79 EmpathyChatManager *chat_manager;
80 gulong chat_manager_chats_changed_id;
82 /* Menu items. */
83 GtkUIManager *ui_manager;
84 GtkAction *menu_conv_insert_smiley;
85 GtkAction *menu_conv_favorite;
86 GtkAction *menu_conv_join_chat;
87 GtkAction *menu_conv_leave_chat;
88 GtkAction *menu_conv_always_urgent;
89 GtkAction *menu_conv_toggle_contacts;
91 GtkAction *menu_edit_cut;
92 GtkAction *menu_edit_copy;
93 GtkAction *menu_edit_paste;
94 GtkAction *menu_edit_find;
96 GtkAction *menu_tabs_next;
97 GtkAction *menu_tabs_prev;
98 GtkAction *menu_tabs_undo_close_tab;
99 GtkAction *menu_tabs_left;
100 GtkAction *menu_tabs_right;
101 GtkAction *menu_tabs_detach;
103 /* Last user action time we acted upon to show a tab */
104 guint32 x_user_action_time;
106 GSettings *gsettings_chat;
107 GSettings *gsettings_notif;
108 GSettings *gsettings_ui;
110 EmpathySoundManager *sound_mgr;
112 gboolean updating_menu;
115 static GList *chat_windows = NULL;
117 static const guint tab_accel_keys[] =
119 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
120 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
123 typedef enum
125 DND_DRAG_TYPE_CONTACT_ID,
126 DND_DRAG_TYPE_INDIVIDUAL_ID,
127 DND_DRAG_TYPE_URI_LIST,
128 DND_DRAG_TYPE_TAB
129 } DndDragType;
131 static const GtkTargetEntry drag_types_dest[] =
133 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
134 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
135 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
136 /* FIXME: disabled because of bug #640513
137 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
138 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
142 static const GtkTargetEntry drag_types_dest_contact[] =
144 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
145 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
148 static const GtkTargetEntry drag_types_dest_file[] =
150 /* must be first to be prioritized, in order to receive the
151 * note's file path from Tomboy instead of an URI */
152 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
153 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
156 static void chat_window_update (EmpathyChatWindow *window,
157 gboolean update_contact_menu);
159 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
160 EmpathyChat *chat);
162 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
163 EmpathyChat *chat);
165 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
166 EmpathyChatWindow *new_window,
167 EmpathyChat *chat);
169 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
170 guint *nb_rooms,
171 guint *nb_private);
173 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_WINDOW)
175 static void
176 chat_window_accel_cb (GtkAccelGroup *accelgroup,
177 GObject *object,
178 guint key,
179 GdkModifierType mod,
180 EmpathyChatWindow *self)
182 gint num = -1;
183 guint i;
185 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
187 if (tab_accel_keys[i] == key)
189 num = i;
190 break;
194 if (num != -1)
195 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
198 static EmpathyChatWindow *
199 chat_window_find_chat (EmpathyChat *chat)
201 GList *l, *ll;
203 for (l = chat_windows; l; l = l->next)
205 EmpathyChatWindow *window = l->data;
207 ll = g_list_find (window->priv->chats, chat);
208 if (ll)
209 return l->data;
212 return NULL;
215 static void
216 remove_all_chats (EmpathyChatWindow *self)
218 g_object_ref (self);
220 while (self->priv->chats)
221 empathy_chat_window_remove_chat (self, self->priv->chats->data);
223 g_object_unref (self);
226 static void
227 confirm_close_response_cb (GtkWidget *dialog,
228 int response,
229 EmpathyChatWindow *window)
231 EmpathyChat *chat;
233 chat = g_object_get_data (G_OBJECT (dialog), "chat");
235 gtk_widget_destroy (dialog);
237 if (response != GTK_RESPONSE_ACCEPT)
238 return;
240 if (chat != NULL)
241 empathy_chat_window_remove_chat (window, chat);
242 else
243 remove_all_chats (window);
246 static void
247 confirm_close (EmpathyChatWindow *self,
248 gboolean close_window,
249 guint n_rooms,
250 EmpathyChat *chat)
252 GtkWidget *dialog;
253 gchar *primary, *secondary;
255 g_return_if_fail (n_rooms > 0);
257 if (n_rooms > 1)
258 g_return_if_fail (chat == NULL);
259 else
260 g_return_if_fail (chat != NULL);
262 /* If there are no chats in this window, how could we possibly have got
263 * here?
265 g_return_if_fail (self->priv->chats != NULL);
267 /* Treat closing a window which only has one tab exactly like closing
268 * that tab.
270 if (close_window && self->priv->chats->next == NULL)
272 close_window = FALSE;
273 chat = self->priv->chats->data;
276 if (close_window)
278 primary = g_strdup (_("Close this window?"));
280 if (n_rooms == 1)
282 gchar *chat_name = empathy_chat_dup_name (chat);
283 secondary = g_strdup_printf (
284 _("Closing this window will leave %s. You will "
285 "not receive any further messages until you "
286 "rejoin it."),
287 chat_name);
288 g_free (chat_name);
290 else
292 secondary = g_strdup_printf (
293 /* Note to translators: the number of chats will
294 * always be at least 2.
296 ngettext (
297 "Closing this window will leave a chat room. You will "
298 "not receive any further messages until you rejoin it.",
299 "Closing this window will leave %u chat rooms. You will "
300 "not receive any further messages until you rejoin them.",
301 n_rooms),
302 n_rooms);
305 else
307 gchar *chat_name = empathy_chat_dup_name (chat);
308 primary = g_strdup_printf (_("Leave %s?"), chat_name);
309 secondary = g_strdup (
310 _("You will not receive any further messages from this chat "
311 "room until you rejoin it."));
312 g_free (chat_name);
315 dialog = gtk_message_dialog_new (
316 GTK_WINDOW (self),
317 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
318 GTK_MESSAGE_WARNING,
319 GTK_BUTTONS_CANCEL,
320 "%s", primary);
322 gtk_window_set_title (GTK_WINDOW (dialog), "");
323 g_object_set (dialog, "secondary-text", secondary, NULL);
325 g_free (primary);
326 g_free (secondary);
328 gtk_dialog_add_button (GTK_DIALOG (dialog),
329 close_window ? _("Close window") : _("Leave room"),
330 GTK_RESPONSE_ACCEPT);
331 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
332 GTK_RESPONSE_ACCEPT);
334 if (!close_window)
335 g_object_set_data (G_OBJECT (dialog), "chat", chat);
337 g_signal_connect (dialog, "response",
338 G_CALLBACK (confirm_close_response_cb), self);
340 gtk_window_present (GTK_WINDOW (dialog));
343 /* Returns TRUE if we should check if the user really wants to leave. If it's
344 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
345 * the user is actually in the room as opposed to having been kicked or gone
346 * offline or something), then we should check.
348 static gboolean
349 chat_needs_close_confirmation (EmpathyChat *chat)
351 return (empathy_chat_is_room (chat) &&
352 empathy_chat_get_tp_chat (chat) != NULL);
355 static void
356 maybe_close_chat (EmpathyChatWindow *window,
357 EmpathyChat *chat)
359 g_return_if_fail (chat != NULL);
361 if (chat_needs_close_confirmation (chat))
362 confirm_close (window, FALSE, 1, chat);
363 else
364 empathy_chat_window_remove_chat (window, chat);
367 static void
368 chat_window_close_clicked_cb (GtkAction *action,
369 EmpathyChat *chat)
371 EmpathyChatWindow *window;
373 window = chat_window_find_chat (chat);
374 maybe_close_chat (window, chat);
377 static void
378 chat_tab_style_updated_cb (GtkWidget *hbox,
379 gpointer user_data)
381 GtkWidget *button;
382 int char_width, h, w;
383 PangoContext *context;
384 PangoFontDescription *font_desc;
385 PangoFontMetrics *metrics;
387 button = g_object_get_data (G_OBJECT (user_data),
388 "chat-window-tab-close-button");
389 context = gtk_widget_get_pango_context (hbox);
391 gtk_style_context_get (gtk_widget_get_style_context (hbox),
392 GTK_STATE_FLAG_NORMAL,
393 "font", &font_desc,
394 NULL);
396 metrics = pango_context_get_metrics (context, font_desc,
397 pango_context_get_language (context));
398 char_width = pango_font_metrics_get_approximate_char_width (metrics);
399 pango_font_metrics_unref (metrics);
401 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
402 GTK_ICON_SIZE_MENU, &w, &h);
404 /* Request at least about 12 chars width plus at least space for the status
405 * image and the close button */
406 gtk_widget_set_size_request (hbox,
407 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
409 gtk_widget_set_size_request (button, w, h);
410 pango_font_description_free (font_desc);
413 static GtkWidget *
414 create_close_button (void)
416 GtkWidget *button, *image;
417 GtkStyleContext *context;
419 button = gtk_button_new ();
421 context = gtk_widget_get_style_context (button);
422 gtk_style_context_add_class (context, "empathy-tab-close-button");
424 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
425 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
427 /* We don't want focus/keynav for the button to avoid clutter, and
428 * Ctrl-W works anyway.
430 gtk_widget_set_can_focus (button, FALSE);
431 gtk_widget_set_can_default (button, FALSE);
433 image = gtk_image_new_from_icon_name ("window-close-symbolic",
434 GTK_ICON_SIZE_MENU);
435 gtk_widget_show (image);
437 gtk_container_add (GTK_CONTAINER (button), image);
439 return button;
442 static GtkWidget *
443 chat_window_create_label (EmpathyChatWindow *window,
444 EmpathyChat *chat,
445 gboolean is_tab_label)
447 GtkWidget *hbox;
448 GtkWidget *name_label;
449 GtkWidget *status_image;
450 GtkWidget *event_box;
451 GtkWidget *event_box_hbox;
452 PangoAttrList *attr_list;
453 PangoAttribute *attr;
455 /* The spacing between the button and the label. */
456 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
458 event_box = gtk_event_box_new ();
459 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
461 name_label = gtk_label_new (NULL);
462 if (is_tab_label)
463 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
465 attr_list = pango_attr_list_new ();
466 attr = pango_attr_scale_new (1/1.2);
467 attr->start_index = 0;
468 attr->end_index = -1;
469 pango_attr_list_insert (attr_list, attr);
470 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
471 pango_attr_list_unref (attr_list);
473 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
474 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
475 g_object_set_data (G_OBJECT (chat),
476 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
477 name_label);
479 status_image = gtk_image_new ();
481 /* Spacing between the icon and label. */
482 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
484 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
485 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
487 g_object_set_data (G_OBJECT (chat),
488 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
489 status_image);
490 g_object_set_data (G_OBJECT (chat),
491 is_tab_label ? "chat-window-tab-tooltip-widget" :
492 "chat-window-menu-tooltip-widget",
493 event_box);
495 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
496 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
498 if (is_tab_label)
500 GtkWidget *close_button;
501 GtkWidget *sending_spinner;
503 sending_spinner = gtk_spinner_new ();
505 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
506 FALSE, FALSE, 0);
507 g_object_set_data (G_OBJECT (chat),
508 "chat-window-tab-sending-spinner",
509 sending_spinner);
511 close_button = create_close_button ();
512 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
513 close_button);
515 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
517 g_signal_connect (close_button,
518 "clicked",
519 G_CALLBACK (chat_window_close_clicked_cb), chat);
521 /* React to theme changes and also setup the size correctly. */
522 g_signal_connect (hbox, "style-updated",
523 G_CALLBACK (chat_tab_style_updated_cb), chat);
526 gtk_widget_show_all (hbox);
528 return hbox;
531 static void
532 _submenu_notify_visible_changed_cb (GObject *object,
533 GParamSpec *pspec,
534 gpointer userdata)
536 g_signal_handlers_disconnect_by_func (object,
537 _submenu_notify_visible_changed_cb, userdata);
539 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
542 static void
543 chat_window_menu_context_update (EmpathyChatWindow *self,
544 gint num_pages)
546 gboolean first_page;
547 gboolean last_page;
548 gboolean wrap_around;
549 gboolean is_connected;
550 gint page_num;
552 page_num = gtk_notebook_get_current_page (
553 GTK_NOTEBOOK (self->priv->notebook));
554 first_page = (page_num == 0);
555 last_page = (page_num == (num_pages - 1));
556 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
557 &wrap_around, NULL);
558 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
560 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
561 wrap_around));
562 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
563 wrap_around));
564 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
565 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
566 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
567 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
570 static void
571 chat_window_conversation_menu_update (EmpathyChatWindow *self)
573 EmpathyTpChat *tp_chat;
574 TpConnection *connection;
575 GtkAction *action;
576 gboolean sensitive = FALSE;
578 g_return_if_fail (self->priv->current_chat != NULL);
580 action = gtk_ui_manager_get_action (self->priv->ui_manager,
581 "/chats_menubar/menu_conv/menu_conv_invite_participant");
582 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
584 if (tp_chat != NULL)
586 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
588 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
589 (tp_connection_get_status (connection, NULL) ==
590 TP_CONNECTION_STATUS_CONNECTED);
593 gtk_action_set_sensitive (action, sensitive);
596 static void
597 chat_window_contact_menu_update (EmpathyChatWindow *self)
599 GtkWidget *menu, *submenu, *orig_submenu;
601 if (self->priv->updating_menu)
602 return;
603 self->priv->updating_menu = TRUE;
605 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
606 "/chats_menubar/menu_contact");
607 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
609 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
611 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
613 if (submenu != NULL)
615 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
616 g_object_set_data (G_OBJECT (submenu), "window", self);
618 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
619 gtk_widget_show (menu);
620 gtk_widget_set_sensitive (menu, TRUE);
622 else
624 gtk_widget_set_sensitive (menu, FALSE);
627 else
629 tp_g_signal_connect_object (orig_submenu,
630 "notify::visible",
631 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
634 self->priv->updating_menu = FALSE;
637 static guint
638 get_all_unread_messages (EmpathyChatWindow *self)
640 GList *l;
641 guint nb = 0;
643 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
644 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
646 return nb;
649 static gchar *
650 get_window_title_name (EmpathyChatWindow *self)
652 gchar *active_name, *ret;
653 guint nb_chats;
654 guint current_unread_msgs;
656 nb_chats = g_list_length (self->priv->chats);
657 g_assert (nb_chats > 0);
659 active_name = empathy_chat_dup_name (self->priv->current_chat);
661 current_unread_msgs = empathy_chat_get_nb_unread_messages (
662 self->priv->current_chat);
664 if (nb_chats == 1)
666 /* only one tab */
667 if (current_unread_msgs == 0)
668 ret = g_strdup (active_name);
669 else
670 ret = g_strdup_printf (ngettext (
671 "%s (%d unread)",
672 "%s (%d unread)", current_unread_msgs),
673 active_name, current_unread_msgs);
675 else
677 guint nb_others = nb_chats - 1;
678 guint all_unread_msgs;
680 all_unread_msgs = get_all_unread_messages (self);
682 if (all_unread_msgs == 0)
684 /* no unread message */
685 ret = g_strdup_printf (ngettext (
686 "%s (and %u other)",
687 "%s (and %u others)", nb_others),
688 active_name, nb_others);
690 else if (all_unread_msgs == current_unread_msgs)
692 /* unread messages are in the current tab */
693 ret = g_strdup_printf (ngettext (
694 "%s (%d unread)",
695 "%s (%d unread)", current_unread_msgs),
696 active_name, current_unread_msgs);
698 else if (current_unread_msgs == 0)
700 /* unread messages are in other tabs */
701 ret = g_strdup_printf (ngettext (
702 "%s (%d unread from others)",
703 "%s (%d unread from others)",
704 all_unread_msgs),
705 active_name, all_unread_msgs);
707 else
709 /* unread messages are in all the tabs */
710 ret = g_strdup_printf (ngettext (
711 "%s (%d unread from all)",
712 "%s (%d unread from all)",
713 all_unread_msgs),
714 active_name, all_unread_msgs);
718 g_free (active_name);
720 return ret;
723 static void
724 chat_window_title_update (EmpathyChatWindow *self)
726 gchar *name;
728 name = get_window_title_name (self);
729 gtk_window_set_title (GTK_WINDOW (self), name);
730 g_free (name);
733 static void
734 chat_window_icon_update (EmpathyChatWindow *self,
735 gboolean new_messages)
737 GdkPixbuf *icon;
738 EmpathyContact *remote_contact;
739 gboolean avatar_in_icon;
740 guint n_chats;
742 n_chats = g_list_length (self->priv->chats);
744 /* Update window icon */
745 if (new_messages)
747 gtk_window_set_icon_name (GTK_WINDOW (self),
748 EMPATHY_IMAGE_MESSAGE);
750 else
752 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
753 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
755 if (n_chats == 1 && avatar_in_icon)
757 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
758 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
759 0, 0);
760 gtk_window_set_icon (GTK_WINDOW (self), icon);
762 if (icon != NULL)
763 g_object_unref (icon);
765 else
767 gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
772 static void
773 chat_window_close_button_update (EmpathyChatWindow *self,
774 gint num_pages)
776 GtkWidget *chat;
777 GtkWidget *chat_close_button;
778 gint i;
780 if (num_pages == 1)
782 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
783 chat_close_button = g_object_get_data (G_OBJECT (chat),
784 "chat-window-tab-close-button");
785 gtk_widget_hide (chat_close_button);
787 else
789 for (i=0; i<num_pages; i++)
791 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
792 chat_close_button = g_object_get_data (G_OBJECT (chat),
793 "chat-window-tab-close-button");
794 gtk_widget_show (chat_close_button);
799 static void
800 chat_window_update (EmpathyChatWindow *self,
801 gboolean update_contact_menu)
803 gint num_pages;
805 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
807 /* Update Tab menu */
808 chat_window_menu_context_update (self, num_pages);
810 chat_window_conversation_menu_update (self);
812 /* If this update is due to a focus-in event, we know the menu will be
813 the same as when we last left it, so no work to do. Besides, if we
814 swap out the menu on a focus-in, we may confuse any external global
815 menu watching. */
816 if (update_contact_menu)
818 chat_window_contact_menu_update (self);
821 chat_window_title_update (self);
823 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
825 chat_window_close_button_update (self, num_pages);
828 static void
829 append_markup_printf (GString *string,
830 const char *format,
831 ...)
833 gchar *tmp;
834 va_list args;
836 va_start (args, format);
838 tmp = g_markup_vprintf_escaped (format, args);
839 g_string_append (string, tmp);
840 g_free (tmp);
842 va_end (args);
845 static void
846 chat_window_update_chat_tab_full (EmpathyChat *chat,
847 gboolean update_contact_menu)
849 EmpathyChatWindow *self;
850 EmpathyContact *remote_contact;
851 gchar *name;
852 const gchar *id;
853 TpAccount *account;
854 const gchar *subject;
855 const gchar *status = NULL;
856 GtkWidget *widget;
857 GString *tooltip;
858 gchar *markup;
859 const gchar *icon_name;
860 GtkWidget *tab_image;
861 GtkWidget *menu_image;
862 GtkWidget *sending_spinner;
863 guint nb_sending;
865 self = chat_window_find_chat (chat);
866 if (!self)
867 return;
869 /* Get information */
870 name = empathy_chat_dup_name (chat);
871 account = empathy_chat_get_account (chat);
872 subject = empathy_chat_get_subject (chat);
873 remote_contact = empathy_chat_get_remote_contact (chat);
875 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
876 "remote_contact=%p",
877 name, tp_proxy_get_object_path (account), subject, remote_contact);
879 /* Update tab image */
880 if (empathy_chat_get_tp_chat (chat) == NULL)
882 /* No TpChat, we are disconnected */
883 icon_name = NULL;
885 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
887 icon_name = EMPATHY_IMAGE_MESSAGE;
889 else if (remote_contact && empathy_chat_is_composing (chat))
891 icon_name = EMPATHY_IMAGE_TYPING;
893 else if (empathy_chat_is_sms_channel (chat))
895 icon_name = EMPATHY_IMAGE_SMS;
897 else if (remote_contact)
899 icon_name = empathy_icon_name_for_contact (remote_contact);
901 else
903 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
906 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
907 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
909 if (icon_name != NULL)
911 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
912 GTK_ICON_SIZE_MENU);
913 gtk_widget_show (tab_image);
914 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
915 GTK_ICON_SIZE_MENU);
916 gtk_widget_show (menu_image);
918 else
920 gtk_widget_hide (tab_image);
921 gtk_widget_hide (menu_image);
924 /* Update the sending spinner */
925 nb_sending = empathy_chat_get_n_messages_sending (chat);
926 sending_spinner = g_object_get_data (G_OBJECT (chat),
927 "chat-window-tab-sending-spinner");
929 g_object_set (sending_spinner,
930 "active", nb_sending > 0,
931 "visible", nb_sending > 0,
932 NULL);
934 /* Update tab tooltip */
935 tooltip = g_string_new (NULL);
937 if (remote_contact)
939 id = empathy_contact_get_id (remote_contact);
940 status = empathy_contact_get_presence_message (remote_contact);
942 else
944 id = name;
947 if (empathy_chat_is_sms_channel (chat))
948 append_markup_printf (tooltip, "%s ", _("SMS:"));
950 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
951 id, tp_account_get_display_name (account));
953 if (nb_sending > 0)
955 char *tmp = g_strdup_printf (
956 ngettext ("Sending %d message",
957 "Sending %d messages",
958 nb_sending),
959 nb_sending);
961 g_string_append (tooltip, "\n");
962 g_string_append (tooltip, tmp);
964 gtk_widget_set_tooltip_text (sending_spinner, tmp);
965 g_free (tmp);
968 if (!TPAW_STR_EMPTY (status))
969 append_markup_printf (tooltip, "\n<i>%s</i>", status);
971 if (!TPAW_STR_EMPTY (subject))
972 append_markup_printf (tooltip, "\n<b>%s</b> %s",
973 _("Topic:"), subject);
975 if (remote_contact && empathy_chat_is_composing (chat))
976 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
978 if (remote_contact != NULL)
980 const gchar * const *types;
982 types = empathy_contact_get_client_types (remote_contact);
983 if (empathy_client_types_contains_mobile_device ((GStrv) types))
985 /* I'm on a mobile device ! */
986 gchar *tmp = name;
988 name = g_strdup_printf ("☎ %s", name);
989 g_free (tmp);
993 markup = g_string_free (tooltip, FALSE);
994 widget = g_object_get_data (G_OBJECT (chat),
995 "chat-window-tab-tooltip-widget");
996 gtk_widget_set_tooltip_markup (widget, markup);
998 widget = g_object_get_data (G_OBJECT (chat),
999 "chat-window-menu-tooltip-widget");
1000 gtk_widget_set_tooltip_markup (widget, markup);
1001 g_free (markup);
1003 /* Update tab and menu label */
1004 if (empathy_chat_is_highlighted (chat))
1006 markup = g_markup_printf_escaped (
1007 "<span color=\"red\" weight=\"bold\">%s</span>",
1008 name);
1010 else
1012 markup = g_markup_escape_text (name, -1);
1015 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1016 gtk_label_set_markup (GTK_LABEL (widget), markup);
1017 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1018 gtk_label_set_markup (GTK_LABEL (widget), markup);
1019 g_free (markup);
1021 /* Update the window if it's the current chat */
1022 if (self->priv->current_chat == chat)
1023 chat_window_update (self, update_contact_menu);
1025 g_free (name);
1028 static void
1029 chat_window_update_chat_tab (EmpathyChat *chat)
1031 chat_window_update_chat_tab_full (chat, TRUE);
1034 static void
1035 chat_window_chat_notify_cb (EmpathyChat *chat)
1037 EmpathyChatWindow *window;
1038 EmpathyContact *old_remote_contact;
1039 EmpathyContact *remote_contact = NULL;
1041 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1042 "chat-window-remote-contact");
1043 remote_contact = empathy_chat_get_remote_contact (chat);
1045 if (old_remote_contact != remote_contact)
1047 /* The remote-contact associated with the chat changed, we need
1048 * to keep track of any change of that contact and update the
1049 * window each time. */
1050 if (remote_contact)
1051 g_signal_connect_swapped (remote_contact, "notify",
1052 G_CALLBACK (chat_window_update_chat_tab), chat);
1054 if (old_remote_contact)
1055 g_signal_handlers_disconnect_by_func (old_remote_contact,
1056 chat_window_update_chat_tab, chat);
1058 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1059 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1062 chat_window_update_chat_tab (chat);
1064 window = chat_window_find_chat (chat);
1065 if (window != NULL)
1066 chat_window_update (window, FALSE);
1069 static void
1070 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1071 EmpathySmiley *smiley,
1072 gpointer user_data)
1074 EmpathyChatWindow *self = user_data;
1075 EmpathyChat *chat;
1076 GtkTextBuffer *buffer;
1078 chat = self->priv->current_chat;
1079 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1081 empathy_chat_insert_smiley (buffer, smiley);
1084 static void
1085 chat_window_conv_activate_cb (GtkAction *action,
1086 EmpathyChatWindow *self)
1088 gboolean is_room;
1089 gboolean active;
1090 EmpathyContact *remote_contact = NULL;
1091 gboolean disconnected;
1093 /* Favorite room menu */
1094 is_room = empathy_chat_is_room (self->priv->current_chat);
1095 if (is_room)
1097 const gchar *room;
1098 TpAccount *account;
1099 gboolean found = FALSE;
1100 EmpathyChatroom *chatroom;
1102 room = empathy_chat_get_id (self->priv->current_chat);
1103 account = empathy_chat_get_account (self->priv->current_chat);
1104 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1105 account, room);
1107 if (chatroom != NULL)
1108 found = empathy_chatroom_is_favorite (chatroom);
1110 DEBUG ("This room %s favorite", found ? "is" : "is not");
1111 gtk_toggle_action_set_active (
1112 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1114 if (chatroom != NULL)
1115 found = empathy_chatroom_is_always_urgent (chatroom);
1117 gtk_toggle_action_set_active (
1118 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1121 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1122 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1124 /* Show contacts menu */
1125 g_object_get (self->priv->current_chat,
1126 "remote-contact", &remote_contact,
1127 "show-contacts", &active,
1128 NULL);
1130 if (remote_contact == NULL)
1132 gtk_toggle_action_set_active (
1133 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1136 /* Menu-items to be visible for MUCs only */
1137 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1138 (remote_contact == NULL));
1140 disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1141 if (disconnected)
1143 gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1144 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1146 else
1148 TpChannel *channel = NULL;
1149 TpContact *self_contact = NULL;
1150 TpHandle self_handle = 0;
1152 channel = (TpChannel *) (empathy_chat_get_tp_chat (
1153 self->priv->current_chat));
1154 self_contact = tp_channel_group_get_self_contact (channel);
1155 if (self_contact == NULL)
1157 /* The channel may not be a group */
1158 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1160 else
1162 self_handle = tp_contact_get_handle (self_contact);
1163 /* There is sometimes a lag between the members-changed signal
1164 emitted on tp-chat and invalidated signal being emitted on the channel.
1165 Leave Chat menu-item should be sensitive only till our self-handle is
1166 a part of channel-members */
1167 gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1168 self_handle != 0);
1171 /* Join Chat is insensitive for a connected chat */
1172 gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1175 if (remote_contact != NULL)
1176 g_object_unref (remote_contact);
1179 static void
1180 chat_window_clear_activate_cb (GtkAction *action,
1181 EmpathyChatWindow *self)
1183 empathy_chat_clear (self->priv->current_chat);
1186 static void
1187 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1188 EmpathyChatWindow *self)
1190 gboolean active;
1191 TpAccount *account;
1192 gchar *name;
1193 const gchar *room;
1194 EmpathyChatroom *chatroom;
1196 active = gtk_toggle_action_get_active (toggle_action);
1197 account = empathy_chat_get_account (self->priv->current_chat);
1198 room = empathy_chat_get_id (self->priv->current_chat);
1199 name = empathy_chat_dup_name (self->priv->current_chat);
1201 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1202 account, room, name);
1204 empathy_chatroom_set_favorite (chatroom, active);
1205 g_object_unref (chatroom);
1206 g_free (name);
1209 static void
1210 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1211 EmpathyChatWindow *self)
1213 gboolean active;
1214 TpAccount *account;
1215 gchar *name;
1216 const gchar *room;
1217 EmpathyChatroom *chatroom;
1219 active = gtk_toggle_action_get_active (toggle_action);
1220 account = empathy_chat_get_account (self->priv->current_chat);
1221 room = empathy_chat_get_id (self->priv->current_chat);
1222 name = empathy_chat_dup_name (self->priv->current_chat);
1224 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1225 account, room, name);
1227 empathy_chatroom_set_always_urgent (chatroom, active);
1228 g_object_unref (chatroom);
1229 g_free (name);
1232 static void
1233 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1234 EmpathyChatWindow *self)
1236 gboolean active;
1238 active = gtk_toggle_action_get_active (toggle_action);
1240 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1243 static void
1244 chat_window_invite_participant_activate_cb (GtkAction *action,
1245 EmpathyChatWindow *self)
1247 GtkWidget *dialog;
1248 EmpathyTpChat *tp_chat;
1249 int response;
1251 g_return_if_fail (self->priv->current_chat != NULL);
1253 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1255 dialog = empathy_invite_participant_dialog_new (
1256 GTK_WINDOW (self), tp_chat);
1258 gtk_widget_show (dialog);
1260 response = gtk_dialog_run (GTK_DIALOG (dialog));
1262 if (response == GTK_RESPONSE_ACCEPT)
1264 TpContact *tp_contact;
1265 EmpathyContact *contact;
1267 tp_contact = empathy_invite_participant_dialog_get_selected (
1268 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1269 if (tp_contact == NULL)
1270 goto out;
1272 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1274 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1276 g_object_unref (contact);
1279 out:
1280 gtk_widget_destroy (dialog);
1283 static void
1284 chat_window_join_chat_activate_cb (GtkAction *action,
1285 EmpathyChatWindow *self)
1287 g_return_if_fail (self->priv->current_chat != NULL);
1289 empathy_chat_join_muc (self->priv->current_chat,
1290 empathy_chat_get_id (self->priv->current_chat));
1293 static void
1294 chat_window_leave_chat_activate_cb (GtkAction *action,
1295 EmpathyChatWindow *self)
1297 EmpathyTpChat * tp_chat;
1299 g_return_if_fail (self->priv->current_chat != NULL);
1301 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1302 if (tp_chat != NULL)
1303 empathy_tp_chat_leave (tp_chat, "");
1306 static void
1307 chat_window_close_activate_cb (GtkAction *action,
1308 EmpathyChatWindow *self)
1310 g_return_if_fail (self->priv->current_chat != NULL);
1312 maybe_close_chat (self, self->priv->current_chat);
1315 static void
1316 chat_window_edit_activate_cb (GtkAction *action,
1317 EmpathyChatWindow *self)
1319 GtkClipboard *clipboard;
1320 GtkTextBuffer *buffer;
1321 gboolean text_available;
1323 g_return_if_fail (self->priv->current_chat != NULL);
1325 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1327 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1328 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1329 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1330 return;
1333 buffer = gtk_text_view_get_buffer (
1334 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1336 if (gtk_text_buffer_get_has_selection (buffer))
1338 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1339 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1341 else
1343 gboolean selection;
1345 selection = empathy_theme_adium_get_has_selection (
1346 self->priv->current_chat->view);
1348 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1349 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1352 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1353 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1354 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1357 static void
1358 chat_window_cut_activate_cb (GtkAction *action,
1359 EmpathyChatWindow *self)
1361 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1363 empathy_chat_cut (self->priv->current_chat);
1366 static void
1367 chat_window_copy_activate_cb (GtkAction *action,
1368 EmpathyChatWindow *self)
1370 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1372 empathy_chat_copy (self->priv->current_chat);
1375 static void
1376 chat_window_paste_activate_cb (GtkAction *action,
1377 EmpathyChatWindow *self)
1379 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1381 empathy_chat_paste (self->priv->current_chat);
1384 static void
1385 chat_window_find_activate_cb (GtkAction *action,
1386 EmpathyChatWindow *self)
1388 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1390 empathy_chat_find (self->priv->current_chat);
1393 static void
1394 chat_window_tabs_next_activate_cb (GtkAction *action,
1395 EmpathyChatWindow *self)
1397 gint index_, numPages;
1398 gboolean wrap_around;
1400 g_object_get (gtk_settings_get_default (),
1401 "gtk-keynav-wrap-around", &wrap_around,
1402 NULL);
1404 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1405 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1407 if (index_ == (numPages - 1) && wrap_around)
1409 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1410 return;
1413 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1416 static void
1417 chat_window_tabs_previous_activate_cb (GtkAction *action,
1418 EmpathyChatWindow *self)
1420 gint index_, numPages;
1421 gboolean wrap_around;
1423 g_object_get (gtk_settings_get_default (),
1424 "gtk-keynav-wrap-around", &wrap_around,
1425 NULL);
1427 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1428 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1430 if (index_ <= 0 && wrap_around)
1432 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1433 numPages - 1);
1434 return;
1437 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1440 static void
1441 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1442 EmpathyChatWindow *self)
1444 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1445 empathy_get_current_action_time ());
1448 static void
1449 chat_window_tabs_left_activate_cb (GtkAction *action,
1450 EmpathyChatWindow *self)
1452 EmpathyChat *chat;
1453 gint index_, num_pages;
1455 chat = self->priv->current_chat;
1456 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1457 if (index_ <= 0)
1458 return;
1460 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1461 index_ - 1);
1463 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1464 chat_window_menu_context_update (self, num_pages);
1467 static void
1468 chat_window_tabs_right_activate_cb (GtkAction *action,
1469 EmpathyChatWindow *self)
1471 EmpathyChat *chat;
1472 gint index_, num_pages;
1474 chat = self->priv->current_chat;
1475 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1477 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1478 index_ + 1);
1480 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1481 chat_window_menu_context_update (self, num_pages);
1484 static EmpathyChatWindow *
1485 empathy_chat_window_new (void)
1487 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1488 "default-width", 580,
1489 "default-height", 480,
1490 "title", _("Chat"),
1491 "role", "chat",
1492 NULL);
1495 static void
1496 chat_window_detach_activate_cb (GtkAction *action,
1497 EmpathyChatWindow *self)
1499 EmpathyChatWindow *new_window;
1500 EmpathyChat *chat;
1502 chat = self->priv->current_chat;
1503 new_window = empathy_chat_window_new ();
1505 empathy_chat_window_move_chat (self, new_window, chat);
1507 gtk_widget_show (GTK_WIDGET (new_window));
1510 static void
1511 chat_window_help_contents_activate_cb (GtkAction *action,
1512 EmpathyChatWindow *self)
1514 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1517 static void
1518 chat_window_help_about_activate_cb (GtkAction *action,
1519 EmpathyChatWindow *self)
1521 empathy_about_dialog_new (GTK_WINDOW (self));
1524 static gboolean
1525 chat_window_delete_event_cb (GtkWidget *dialog,
1526 GdkEvent *event,
1527 EmpathyChatWindow *self)
1529 EmpathyChat *chat = NULL;
1530 guint n_rooms = 0;
1531 GList *l;
1533 DEBUG ("Delete event received");
1535 for (l = self->priv->chats; l != NULL; l = l->next)
1537 if (chat_needs_close_confirmation (l->data))
1539 chat = l->data;
1540 n_rooms++;
1544 if (n_rooms > 0)
1546 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1548 else
1550 remove_all_chats (self);
1553 return TRUE;
1556 static void
1557 chat_window_composing_cb (EmpathyChat *chat,
1558 gboolean is_composing,
1559 EmpathyChatWindow *self)
1561 chat_window_update_chat_tab (chat);
1564 static void
1565 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1566 gboolean urgent)
1568 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1571 static void
1572 chat_window_notification_closed_cb (NotifyNotification *notify,
1573 EmpathyChatWindow *self)
1575 g_object_unref (notify);
1576 if (self->priv->notification == notify)
1577 self->priv->notification = NULL;
1580 static void
1581 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1582 EmpathyMessage *message,
1583 EmpathyChat *chat)
1585 EmpathyContact *sender;
1586 const gchar *header;
1587 char *escaped;
1588 const char *body;
1589 GdkPixbuf *pixbuf;
1590 gboolean res, has_x_canonical_append;
1591 NotifyNotification *notification = self->priv->notification;
1593 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1594 return;
1596 res = g_settings_get_boolean (self->priv->gsettings_notif,
1597 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1599 if (!res)
1600 return;
1602 sender = empathy_message_get_sender (message);
1603 header = empathy_contact_get_alias (sender);
1604 body = empathy_message_get_body (message);
1605 escaped = g_markup_escape_text (body, -1);
1607 has_x_canonical_append = empathy_notify_manager_has_capability (
1608 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1610 if (notification != NULL && !has_x_canonical_append)
1612 /* if the notification server supports x-canonical-append, it is
1613 better to not use notify_notification_update to avoid
1614 overwriting the current notification message */
1615 notify_notification_update (notification,
1616 header, escaped, NULL);
1618 else
1620 /* if the notification server supports x-canonical-append,
1621 the hint will be added, so that the message from the
1622 just created notification will be automatically appended
1623 to an existing notification with the same title.
1624 In this way the previous message will not be lost: the new
1625 message will appear below it, in the same notification */
1626 const gchar *category = empathy_chat_is_room (chat)
1627 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1628 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1630 notification = empathy_notify_manager_create_notification (header,
1631 escaped, NULL);
1633 if (self->priv->notification == NULL)
1634 self->priv->notification = notification;
1636 tp_g_signal_connect_object (notification, "closed",
1637 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1639 if (has_x_canonical_append)
1641 /* We have to set a not empty string to keep libnotify happy */
1642 notify_notification_set_hint_string (notification,
1643 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1646 notify_notification_set_hint (notification,
1647 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1650 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1651 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1653 if (pixbuf != NULL)
1655 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1656 g_object_unref (pixbuf);
1659 notify_notification_show (notification, NULL);
1661 g_free (escaped);
1664 static gboolean
1665 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1667 gboolean has_focus;
1669 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1671 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1673 return has_focus;
1676 static void
1677 chat_window_new_message_cb (EmpathyChat *chat,
1678 EmpathyMessage *message,
1679 gboolean pending,
1680 gboolean should_highlight,
1681 EmpathyChatWindow *self)
1683 gboolean has_focus;
1684 gboolean needs_urgency;
1685 EmpathyContact *sender;
1687 has_focus = empathy_chat_window_has_focus (self);
1689 /* - if we're the sender, we play the sound if it's specified in the
1690 * preferences and we're not away.
1691 * - if we receive a message, we play the sound if it's specified in the
1692 * preferences and the window does not have focus on the chat receiving
1693 * the message.
1696 sender = empathy_message_get_sender (message);
1698 if (empathy_contact_is_user (sender))
1700 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1701 EMPATHY_SOUND_MESSAGE_OUTGOING);
1702 return;
1705 if (has_focus && self->priv->current_chat == chat)
1707 /* window and tab are focused so consider the message to be read */
1709 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1710 empathy_chat_messages_read (chat);
1711 return;
1714 /* Update the chat tab if this is the first unread message */
1715 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1717 chat_window_update_chat_tab (chat);
1720 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1721 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1722 * an unamed MUC (msn-like).
1723 * In case of a MUC, we set urgency if either:
1724 * a) the chatroom's always_urgent property is TRUE
1725 * b) the message contains our alias
1727 if (empathy_chat_is_room (chat))
1729 TpAccount *account;
1730 const gchar *room;
1731 EmpathyChatroom *chatroom;
1733 account = empathy_chat_get_account (chat);
1734 room = empathy_chat_get_id (chat);
1736 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1737 account, room);
1739 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1740 needs_urgency = TRUE;
1741 else
1742 needs_urgency = should_highlight;
1744 else
1746 needs_urgency = TRUE;
1749 if (needs_urgency)
1751 if (!has_focus)
1752 chat_window_set_urgency_hint (self, TRUE);
1754 /* Pending messages have already been displayed and notified in the
1755 * approver, so we don't display a notification and play a sound
1756 * for those */
1757 if (!pending)
1759 empathy_sound_manager_play (self->priv->sound_mgr,
1760 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1762 chat_window_show_or_update_notification (self, message, chat);
1766 /* update the number of unread messages and the window icon */
1767 chat_window_title_update (self);
1768 chat_window_icon_update (self, TRUE);
1771 static void
1772 chat_window_command_part (EmpathyChat *chat,
1773 GStrv strv)
1775 EmpathyChat *chat_to_be_parted;
1776 EmpathyTpChat *tp_chat = NULL;
1778 if (strv[1] == NULL)
1780 /* No chatroom ID specified */
1781 tp_chat = empathy_chat_get_tp_chat (chat);
1783 if (tp_chat)
1784 empathy_tp_chat_leave (tp_chat, "");
1786 return;
1789 chat_to_be_parted = empathy_chat_window_find_chat (
1790 empathy_chat_get_account (chat), strv[1], FALSE);
1792 if (chat_to_be_parted != NULL)
1794 /* Found a chatroom matching the specified ID */
1795 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1797 if (tp_chat)
1798 empathy_tp_chat_leave (tp_chat, strv[2]);
1800 else
1802 gchar *message;
1804 /* Going by the syntax of PART command:
1806 * /PART [<chatroom-ID>] [<reason>]
1808 * Chatroom-ID is not a must to specify a reason.
1809 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1810 * MUC then the current chatroom should be parted and srtv[1] should
1811 * be treated as part of the optional part-message. */
1812 message = g_strconcat (strv[1], " ", strv[2], NULL);
1813 tp_chat = empathy_chat_get_tp_chat (chat);
1815 if (tp_chat)
1816 empathy_tp_chat_leave (tp_chat, message);
1818 g_free (message);
1822 static GtkNotebook *
1823 notebook_create_window_cb (GtkNotebook *source,
1824 GtkWidget *page,
1825 gint x,
1826 gint y,
1827 gpointer user_data)
1829 EmpathyChatWindow *window, *new_window;
1830 EmpathyChat *chat;
1832 chat = EMPATHY_CHAT (page);
1833 window = chat_window_find_chat (chat);
1835 new_window = empathy_chat_window_new ();
1837 DEBUG ("Detach hook called");
1839 empathy_chat_window_move_chat (window, new_window, chat);
1841 gtk_widget_show (GTK_WIDGET (new_window));
1842 gtk_window_move (GTK_WINDOW (new_window), x, y);
1844 return NULL;
1847 static void
1848 chat_window_page_switched_cb (GtkNotebook *notebook,
1849 GtkWidget *child,
1850 gint page_num,
1851 EmpathyChatWindow *self)
1853 EmpathyChat *chat = EMPATHY_CHAT (child);
1855 DEBUG ("Page switched");
1857 if (self->priv->page_added)
1859 self->priv->page_added = FALSE;
1860 empathy_chat_scroll_down (chat);
1862 else if (self->priv->current_chat == chat)
1864 return;
1867 self->priv->current_chat = chat;
1868 empathy_chat_messages_read (chat);
1870 chat_window_update_chat_tab (chat);
1873 static void
1874 chat_window_page_added_cb (GtkNotebook *notebook,
1875 GtkWidget *child,
1876 guint page_num,
1877 EmpathyChatWindow *self)
1879 EmpathyChat *chat;
1881 /* If we just received DND to the same window, we don't want
1882 * to do anything here like removing the tab and then readding
1883 * it, so we return here and in "page-added".
1885 if (self->priv->dnd_same_window)
1887 DEBUG ("Page added (back to the same window)");
1888 self->priv->dnd_same_window = FALSE;
1889 return;
1892 DEBUG ("Page added");
1894 /* Get chat object */
1895 chat = EMPATHY_CHAT (child);
1897 /* Connect chat signals for this window */
1898 g_signal_connect (chat, "composing",
1899 G_CALLBACK (chat_window_composing_cb), self);
1900 g_signal_connect (chat, "new-message",
1901 G_CALLBACK (chat_window_new_message_cb), self);
1902 g_signal_connect (chat, "part-command-entered",
1903 G_CALLBACK (chat_window_command_part), NULL);
1904 g_signal_connect (chat, "notify::tp-chat",
1905 G_CALLBACK (chat_window_update_chat_tab), self);
1907 /* Set flag so we know to perform some special operations on
1908 * switch page due to the new page being added.
1910 self->priv->page_added = TRUE;
1912 /* Get list of chats up to date */
1913 self->priv->chats = g_list_append (self->priv->chats, chat);
1915 chat_window_update_chat_tab (chat);
1918 static void
1919 chat_window_page_removed_cb (GtkNotebook *notebook,
1920 GtkWidget *child,
1921 guint page_num,
1922 EmpathyChatWindow *self)
1924 EmpathyChat *chat;
1926 /* If we just received DND to the same window, we don't want
1927 * to do anything here like removing the tab and then readding
1928 * it, so we return here and in "page-added".
1930 if (self->priv->dnd_same_window)
1932 DEBUG ("Page removed (and will be readded to same window)");
1933 return;
1936 DEBUG ("Page removed");
1938 /* Get chat object */
1939 chat = EMPATHY_CHAT (child);
1941 /* Disconnect all signal handlers for this chat and this window */
1942 g_signal_handlers_disconnect_by_func (chat,
1943 G_CALLBACK (chat_window_composing_cb), self);
1944 g_signal_handlers_disconnect_by_func (chat,
1945 G_CALLBACK (chat_window_new_message_cb), self);
1946 g_signal_handlers_disconnect_by_func (chat,
1947 G_CALLBACK (chat_window_update_chat_tab), self);
1949 /* Keep list of chats up to date */
1950 self->priv->chats = g_list_remove (self->priv->chats, chat);
1951 empathy_chat_messages_read (chat);
1953 if (self->priv->chats == NULL)
1955 gtk_widget_destroy (GTK_WIDGET (self));
1957 else
1959 chat_window_update (self, TRUE);
1963 static gboolean
1964 chat_window_focus_in_event_cb (GtkWidget *widget,
1965 GdkEvent *event,
1966 EmpathyChatWindow *self)
1968 empathy_chat_messages_read (self->priv->current_chat);
1970 chat_window_set_urgency_hint (self, FALSE);
1972 /* Update the title, since we now mark all unread messages as read. */
1973 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1975 return FALSE;
1978 static void
1979 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1980 EmpathyChatWindow *self)
1982 chat_window_contact_menu_update (self);
1985 static gboolean
1986 chat_window_focus_out_event_cb (GtkWidget *widget,
1987 GdkEvent *event,
1988 EmpathyChatWindow *self)
1990 if (self->priv->individual_mgr != NULL)
1991 return FALSE;
1993 /* Keep the individual manager alive so we won't fetch everything from Folks
1994 * each time we need to use it. Loading FolksAggregator can takes quite a
1995 * while (if user has a huge LDAP abook for example) and it blocks
1996 * the mainloop during most of this loading. We workaround this by loading
1997 * it when the chat window has been unfocused and so, hopefully, not impact
1998 * the reactivity of the chat window too much.
2000 * The individual manager (and so Folks) is needed to know to which
2001 * FolksIndividual a TpContact belongs, including:
2002 * - empathy_chat_get_contact_menu: to list all the personas of the contact
2003 * - empathy_display_individual_info: to invoke gnome-contacts with the
2004 * FolksIndividual.id of the contact
2005 * - drag_data_received_individual_id: to find the individual associated
2006 * with the ID we received from the DnD in order to invite him.
2008 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2010 if (!empathy_individual_manager_get_contacts_loaded (
2011 self->priv->individual_mgr))
2013 /* We want to update the contact menu when Folks is loaded so we can
2014 * list all the personas of the contact. */
2015 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2016 G_CALLBACK (contacts_loaded_cb), self, 0);
2019 g_object_notify (G_OBJECT (self), "individual-manager");
2021 return FALSE;
2024 static gboolean
2025 chat_window_drag_drop (GtkWidget *widget,
2026 GdkDragContext *context,
2027 int x,
2028 int y,
2029 guint time_,
2030 EmpathyChatWindow *self)
2032 GdkAtom target;
2034 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2035 if (target == GDK_NONE)
2036 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2038 if (target != GDK_NONE)
2040 gtk_drag_get_data (widget, context, target, time_);
2041 return TRUE;
2044 return FALSE;
2047 static gboolean
2048 chat_window_drag_motion (GtkWidget *widget,
2049 GdkDragContext *context,
2050 int x,
2051 int y,
2052 guint time_,
2053 EmpathyChatWindow *self)
2055 GdkAtom target;
2057 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2059 if (target != GDK_NONE)
2061 /* This is a file drag. Ensure the contact is online and set the
2062 drag type to COPY. Note that it's possible that the tab will
2063 be switched by GTK+ after a timeout from drag_motion without
2064 getting another drag_motion to disable the drop. You have
2065 to hold your mouse really still.
2067 EmpathyContact *contact;
2069 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2071 /* contact is NULL for multi-user chats. We don't do
2072 * file transfers to MUCs. We also don't send files
2073 * to offline contacts or contacts that don't support
2074 * file transfer.
2076 if ((contact == NULL) || !empathy_contact_is_online (contact))
2078 gdk_drag_status (context, 0, time_);
2079 return FALSE;
2082 if (!(empathy_contact_get_capabilities (contact)
2083 & EMPATHY_CAPABILITIES_FT))
2085 gdk_drag_status (context, 0, time_);
2086 return FALSE;
2089 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2090 return TRUE;
2093 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2094 if (target != GDK_NONE)
2096 /* This is a drag of a contact from a contact list. Set to COPY.
2097 FIXME: If this drag is to a MUC window, it invites the user.
2098 Otherwise, it opens a chat. Should we use a different drag
2099 type for invites? Should we allow ASK?
2101 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2102 return TRUE;
2105 return FALSE;
2108 static void
2109 drag_data_received_individual_id (EmpathyChatWindow *self,
2110 GtkWidget *widget,
2111 GdkDragContext *context,
2112 int x,
2113 int y,
2114 GtkSelectionData *selection,
2115 guint info,
2116 guint time_)
2118 const gchar *id;
2119 FolksIndividual *individual;
2120 EmpathyTpChat *chat;
2121 TpContact *tp_contact;
2122 TpConnection *conn;
2123 EmpathyContact *contact;
2125 id = (const gchar *) gtk_selection_data_get_data (selection);
2127 DEBUG ("DND invididual %s", id);
2129 if (self->priv->current_chat == NULL)
2130 goto out;
2132 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2133 if (chat == NULL)
2134 goto out;
2136 if (!empathy_tp_chat_can_add_contact (chat))
2138 DEBUG ("Can't invite contact to %s",
2139 tp_proxy_get_object_path (chat));
2140 goto out;
2143 if (self->priv->individual_mgr == NULL)
2144 /* Not likely as we have to focus out the chat window in order to start
2145 * the DnD but best to be safe. */
2146 goto out;
2148 individual = empathy_individual_manager_lookup_member (
2149 self->priv->individual_mgr, id);
2150 if (individual == NULL)
2152 DEBUG ("Failed to find individual %s", id);
2153 goto out;
2156 conn = tp_channel_get_connection ((TpChannel *) chat);
2157 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2158 if (tp_contact == NULL)
2160 DEBUG ("Can't find a TpContact on connection %s for %s",
2161 tp_proxy_get_object_path (conn), id);
2162 goto out;
2165 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2166 tp_channel_get_identifier ((TpChannel *) chat));
2168 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2169 empathy_tp_chat_add (chat, contact, NULL);
2170 g_object_unref (contact);
2172 out:
2173 gtk_drag_finish (context, TRUE, FALSE, time_);
2176 static void
2177 chat_window_drag_data_received (GtkWidget *widget,
2178 GdkDragContext *context,
2179 int x,
2180 int y,
2181 GtkSelectionData *selection,
2182 guint info,
2183 guint time_,
2184 EmpathyChatWindow *self)
2186 if (info == DND_DRAG_TYPE_CONTACT_ID)
2188 EmpathyChat *chat = NULL;
2189 EmpathyChatWindow *old_window;
2190 TpAccount *account = NULL;
2191 EmpathyClientFactory *factory;
2192 const gchar *id;
2193 gchar **strv;
2194 const gchar *account_id;
2195 const gchar *contact_id;
2197 id = (const gchar*) gtk_selection_data_get_data (selection);
2199 factory = empathy_client_factory_dup ();
2201 DEBUG ("DND contact from roster with id:'%s'", id);
2203 strv = g_strsplit (id, ":", 2);
2204 if (g_strv_length (strv) == 2)
2206 account_id = strv[0];
2207 contact_id = strv[1];
2209 account = tp_simple_client_factory_ensure_account (
2210 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2212 g_object_unref (factory);
2213 if (account != NULL)
2214 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2217 if (account == NULL)
2219 g_strfreev (strv);
2220 gtk_drag_finish (context, FALSE, FALSE, time_);
2221 return;
2224 if (!chat)
2226 empathy_chat_with_contact_id (account, contact_id,
2227 empathy_get_current_action_time (), NULL, NULL);
2229 g_strfreev (strv);
2230 return;
2233 g_strfreev (strv);
2235 old_window = chat_window_find_chat (chat);
2236 if (old_window)
2238 if (old_window == self)
2240 gtk_drag_finish (context, TRUE, FALSE, time_);
2241 return;
2244 empathy_chat_window_move_chat (old_window, self, chat);
2246 else
2248 empathy_chat_window_add_chat (self, chat);
2251 /* Added to take care of any outstanding chat events */
2252 empathy_chat_window_present_chat (chat,
2253 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2255 /* We should return TRUE to remove the data when doing
2256 * GDK_ACTION_MOVE, but we don't here otherwise it has
2257 * weird consequences, and we handle that internally
2258 * anyway with add_chat () and remove_chat ().
2260 gtk_drag_finish (context, TRUE, FALSE, time_);
2262 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2264 drag_data_received_individual_id (self, widget, context, x, y,
2265 selection, info, time_);
2267 else if (info == DND_DRAG_TYPE_URI_LIST)
2269 EmpathyContact *contact;
2270 const gchar *data;
2272 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2274 /* contact is NULL when current_chat is a multi-user chat.
2275 * We don't do file transfers to MUCs, so just cancel the drag.
2277 if (contact == NULL)
2279 gtk_drag_finish (context, TRUE, FALSE, time_);
2280 return;
2283 data = (const gchar *) gtk_selection_data_get_data (selection);
2284 empathy_send_file_from_uri_list (contact, data);
2286 gtk_drag_finish (context, TRUE, FALSE, time_);
2288 else if (info == DND_DRAG_TYPE_TAB)
2290 EmpathyChat **chat;
2291 EmpathyChatWindow *old_window = NULL;
2293 DEBUG ("DND tab");
2295 chat = (void *) gtk_selection_data_get_data (selection);
2296 old_window = chat_window_find_chat (*chat);
2298 if (old_window)
2300 self->priv->dnd_same_window = (old_window == self);
2302 DEBUG ("DND tab (within same window: %s)",
2303 self->priv->dnd_same_window ? "Yes" : "No");
2306 else
2308 DEBUG ("DND from unknown source");
2309 gtk_drag_finish (context, FALSE, FALSE, time_);
2313 static void
2314 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2315 guint num_chats_in_manager,
2316 EmpathyChatWindow *self)
2318 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2319 num_chats_in_manager > 0);
2322 static void
2323 chat_window_finalize (GObject *object)
2325 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2327 DEBUG ("Finalized: %p", object);
2329 g_object_unref (self->priv->ui_manager);
2330 g_object_unref (self->priv->chatroom_manager);
2331 g_object_unref (self->priv->notify_mgr);
2332 g_object_unref (self->priv->gsettings_chat);
2333 g_object_unref (self->priv->gsettings_notif);
2334 g_object_unref (self->priv->gsettings_ui);
2335 g_object_unref (self->priv->sound_mgr);
2336 g_clear_object (&self->priv->individual_mgr);
2338 if (self->priv->notification != NULL)
2340 notify_notification_close (self->priv->notification, NULL);
2341 self->priv->notification = NULL;
2344 if (self->priv->contact_targets)
2345 gtk_target_list_unref (self->priv->contact_targets);
2347 if (self->priv->file_targets)
2348 gtk_target_list_unref (self->priv->file_targets);
2350 if (self->priv->chat_manager)
2352 g_signal_handler_disconnect (self->priv->chat_manager,
2353 self->priv->chat_manager_chats_changed_id);
2354 g_object_unref (self->priv->chat_manager);
2355 self->priv->chat_manager = NULL;
2358 chat_windows = g_list_remove (chat_windows, self);
2360 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2363 static void
2364 chat_window_get_property (GObject *object,
2365 guint property_id,
2366 GValue *value,
2367 GParamSpec *pspec)
2369 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2371 switch (property_id)
2373 case PROP_INDIVIDUAL_MGR:
2374 g_value_set_object (value, self->priv->individual_mgr);
2375 default:
2376 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2377 break;
2381 static void
2382 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2384 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2385 GParamSpec *spec;
2387 object_class->get_property = chat_window_get_property;
2388 object_class->finalize = chat_window_finalize;
2390 spec = g_param_spec_object ("individual-manager", "individual-manager",
2391 "EmpathyIndividualManager",
2392 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2393 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2394 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2396 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2399 static void
2400 empathy_chat_window_init (EmpathyChatWindow *self)
2402 GtkBuilder *gui;
2403 GtkAccelGroup *accel_group;
2404 GClosure *closure;
2405 GtkWidget *menu;
2406 GtkWidget *submenu;
2407 guint i;
2408 GtkWidget *chat_vbox;
2409 gchar *filename;
2410 EmpathySmileyManager *smiley_manager;
2412 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2413 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2415 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2416 gui = tpaw_builder_get_file (filename,
2417 "chat_vbox", &chat_vbox,
2418 "ui_manager", &self->priv->ui_manager,
2419 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2420 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2421 "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2422 "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2423 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2424 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2425 "menu_edit_cut", &self->priv->menu_edit_cut,
2426 "menu_edit_copy", &self->priv->menu_edit_copy,
2427 "menu_edit_paste", &self->priv->menu_edit_paste,
2428 "menu_edit_find", &self->priv->menu_edit_find,
2429 "menu_tabs_next", &self->priv->menu_tabs_next,
2430 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2431 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2432 "menu_tabs_left", &self->priv->menu_tabs_left,
2433 "menu_tabs_right", &self->priv->menu_tabs_right,
2434 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2435 NULL);
2436 g_free (filename);
2438 tpaw_builder_connect (gui, self,
2439 "menu_conv", "activate", chat_window_conv_activate_cb,
2440 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2441 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2442 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2443 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2444 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2445 "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2446 "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2447 "menu_conv_close", "activate", chat_window_close_activate_cb,
2448 "menu_edit", "activate", chat_window_edit_activate_cb,
2449 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2450 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2451 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2452 "menu_edit_find", "activate", chat_window_find_activate_cb,
2453 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2454 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2455 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2456 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2457 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2458 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2459 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2460 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2461 NULL);
2463 empathy_set_css_provider (GTK_WIDGET (self));
2465 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2466 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2467 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2468 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2470 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2472 self->priv->notebook = gtk_notebook_new ();
2474 g_signal_connect (self->priv->notebook, "create-window",
2475 G_CALLBACK (notebook_create_window_cb), self);
2477 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2479 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2480 "EmpathyChatWindow");
2481 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2482 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2483 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2484 gtk_widget_show (self->priv->notebook);
2486 /* Set up accels */
2487 accel_group = gtk_accel_group_new ();
2488 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2490 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2492 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2493 NULL);
2495 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2496 closure);
2499 g_object_unref (accel_group);
2501 /* Set up drag target lists */
2502 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2503 G_N_ELEMENTS (drag_types_dest_contact));
2505 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2506 G_N_ELEMENTS (drag_types_dest_file));
2508 /* Set up smiley menu */
2509 smiley_manager = empathy_smiley_manager_dup_singleton ();
2510 submenu = empathy_smiley_menu_new (smiley_manager,
2511 chat_window_insert_smiley_activate_cb, self);
2513 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2514 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2515 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2516 g_object_unref (smiley_manager);
2518 /* Set up signals we can't do with ui file since we may need to
2519 * block/unblock them at some later stage.
2522 g_signal_connect (self, "delete_event",
2523 G_CALLBACK (chat_window_delete_event_cb), self);
2524 g_signal_connect (self, "focus_in_event",
2525 G_CALLBACK (chat_window_focus_in_event_cb), self);
2526 g_signal_connect (self, "focus_out_event",
2527 G_CALLBACK (chat_window_focus_out_event_cb), self);
2528 g_signal_connect_after (self->priv->notebook, "switch_page",
2529 G_CALLBACK (chat_window_page_switched_cb), self);
2530 g_signal_connect (self->priv->notebook, "page_added",
2531 G_CALLBACK (chat_window_page_added_cb), self);
2532 g_signal_connect (self->priv->notebook, "page_removed",
2533 G_CALLBACK (chat_window_page_removed_cb), self);
2535 /* Set up drag and drop */
2536 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2537 GTK_DEST_DEFAULT_HIGHLIGHT,
2538 drag_types_dest,
2539 G_N_ELEMENTS (drag_types_dest),
2540 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2542 /* connect_after to allow GtkNotebook's built-in tab switching */
2543 g_signal_connect_after (self->priv->notebook, "drag-motion",
2544 G_CALLBACK (chat_window_drag_motion), self);
2545 g_signal_connect (self->priv->notebook, "drag-data-received",
2546 G_CALLBACK (chat_window_drag_data_received), self);
2547 g_signal_connect (self->priv->notebook, "drag-drop",
2548 G_CALLBACK (chat_window_drag_drop), self);
2550 chat_windows = g_list_prepend (chat_windows, self);
2552 /* Set up private details */
2553 self->priv->chats = NULL;
2554 self->priv->current_chat = NULL;
2555 self->priv->notification = NULL;
2557 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2559 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2560 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2561 self->priv->chat_manager, "closed-chats-changed",
2562 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2564 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2565 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2567 g_object_ref (self->priv->ui_manager);
2568 g_object_unref (gui);
2571 /* Returns the window to open a new tab in if there is a suitable window,
2572 * otherwise, returns NULL indicating that a new window should be added.
2574 static EmpathyChatWindow *
2575 empathy_chat_window_get_default (gboolean room)
2577 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2578 GList *l;
2579 gboolean separate_windows = TRUE;
2581 separate_windows = g_settings_get_boolean (gsettings,
2582 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2584 g_object_unref (gsettings);
2586 if (separate_windows)
2587 /* Always create a new window */
2588 return NULL;
2590 for (l = chat_windows; l; l = l->next)
2592 EmpathyChatWindow *chat_window;
2593 guint nb_rooms, nb_private;
2595 chat_window = l->data;
2597 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2599 /* Skip the window if there aren't any rooms in it */
2600 if (room && nb_rooms == 0)
2601 continue;
2603 /* Skip the window if there aren't any 1-1 chats in it */
2604 if (!room && nb_private == 0)
2605 continue;
2607 return chat_window;
2610 return NULL;
2613 static void
2614 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2615 EmpathyChat *chat)
2617 GtkWidget *label;
2618 GtkWidget *popup_label;
2619 GtkWidget *child;
2620 GValue value = { 0, };
2622 g_return_if_fail (self != NULL);
2623 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2625 /* Reference the chat object */
2626 g_object_ref (chat);
2628 /* If this window has just been created, position it */
2629 if (self->priv->chats == NULL)
2631 const gchar *name = "chat-window";
2632 gboolean separate_windows;
2634 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2635 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2637 if (empathy_chat_is_room (chat))
2638 name = "room-window";
2640 if (separate_windows)
2642 gint x, y;
2644 /* Save current position of the window */
2645 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2647 /* First bind to the 'generic' name. So new window for which we didn't
2648 * save a geometry yet will have the geometry of the last saved
2649 * window (bgo #601191). */
2650 empathy_geometry_bind (GTK_WINDOW (self), name);
2652 /* Restore previous position of the window so the newly created window
2653 * won't be in the same position as the latest saved window and so
2654 * completely hide it. */
2655 gtk_window_move (GTK_WINDOW (self), x, y);
2657 /* Then bind it to the name of the contact/room so we'll save the
2658 * geometry specific to this window */
2659 name = empathy_chat_get_id (chat);
2662 empathy_geometry_bind (GTK_WINDOW (self), name);
2665 child = GTK_WIDGET (chat);
2666 label = chat_window_create_label (self, chat, TRUE);
2667 popup_label = chat_window_create_label (self, chat, FALSE);
2668 gtk_widget_show (child);
2670 g_signal_connect (chat, "notify::name",
2671 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2672 g_signal_connect (chat, "notify::subject",
2673 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2674 g_signal_connect (chat, "notify::remote-contact",
2675 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2676 g_signal_connect (chat, "notify::sms-channel",
2677 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2678 g_signal_connect (chat, "notify::n-messages-sending",
2679 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2680 g_signal_connect (chat, "notify::nb-unread-messages",
2681 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2682 chat_window_chat_notify_cb (chat);
2684 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2685 popup_label);
2686 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2687 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2688 g_value_init (&value, G_TYPE_BOOLEAN);
2689 g_value_set_boolean (&value, TRUE);
2690 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2691 child, "tab-expand" , &value);
2692 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2693 child, "tab-fill" , &value);
2694 g_value_unset (&value);
2696 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2699 static void
2700 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2701 EmpathyChat *chat)
2703 gint position;
2704 EmpathyContact *remote_contact;
2705 EmpathyChatManager *chat_manager;
2707 g_return_if_fail (self != NULL);
2708 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2710 g_signal_handlers_disconnect_by_func (chat,
2711 chat_window_chat_notify_cb, NULL);
2713 remote_contact = g_object_get_data (G_OBJECT (chat),
2714 "chat-window-remote-contact");
2716 if (remote_contact)
2718 g_signal_handlers_disconnect_by_func (remote_contact,
2719 chat_window_update_chat_tab, chat);
2722 chat_manager = empathy_chat_manager_dup_singleton ();
2723 empathy_chat_manager_closed_chat (chat_manager, chat);
2724 g_object_unref (chat_manager);
2726 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2727 GTK_WIDGET (chat));
2728 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2730 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2732 g_object_unref (chat);
2735 static void
2736 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2737 EmpathyChatWindow *new_window,
2738 EmpathyChat *chat)
2740 GtkWidget *widget;
2742 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2743 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2744 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2746 widget = GTK_WIDGET (chat);
2748 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2749 G_OBJECT (widget)->ref_count);
2751 /* We reference here to make sure we don't loose the widget
2752 * and the EmpathyChat object during the move.
2754 g_object_ref (chat);
2755 g_object_ref (widget);
2757 empathy_chat_window_remove_chat (old_window, chat);
2758 empathy_chat_window_add_chat (new_window, chat);
2760 g_object_unref (widget);
2761 g_object_unref (chat);
2764 static void
2765 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2766 EmpathyChat *chat)
2768 gint page_num;
2770 g_return_if_fail (self != NULL);
2771 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2773 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2774 GTK_WIDGET (chat));
2776 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2777 page_num);
2780 EmpathyChat *
2781 empathy_chat_window_find_chat (TpAccount *account,
2782 const gchar *id,
2783 gboolean sms_channel)
2785 GList *l;
2787 g_return_val_if_fail (!TPAW_STR_EMPTY (id), NULL);
2789 for (l = chat_windows; l; l = l->next)
2791 EmpathyChatWindow *window = l->data;
2792 GList *ll;
2794 for (ll = window->priv->chats; ll; ll = ll->next)
2796 EmpathyChat *chat;
2798 chat = ll->data;
2800 if (account == empathy_chat_get_account (chat) &&
2801 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2802 sms_channel == empathy_chat_is_sms_channel (chat))
2803 return chat;
2807 return NULL;
2810 EmpathyChatWindow *
2811 empathy_chat_window_present_chat (EmpathyChat *chat,
2812 gint64 timestamp)
2814 EmpathyChatWindow *self;
2815 guint32 x_timestamp;
2817 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2819 self = chat_window_find_chat (chat);
2821 /* If the chat has no window, create one */
2822 if (self == NULL)
2824 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2825 if (!self)
2827 self = empathy_chat_window_new ();
2829 /* we want to display the newly created window even if we
2830 * don't present it */
2831 gtk_widget_show (GTK_WIDGET (self));
2834 empathy_chat_window_add_chat (self, chat);
2837 /* Don't force the window to show itself when it wasn't
2838 * an action by the user
2840 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2841 return self;
2843 if (x_timestamp != GDK_CURRENT_TIME)
2845 /* Don't present or switch tab if the action was earlier than the
2846 * last actions X time, accounting for overflow and the first ever
2847 * presentation */
2849 if (self->priv->x_user_action_time != 0
2850 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2851 return self;
2853 self->priv->x_user_action_time = x_timestamp;
2856 empathy_chat_window_switch_to_chat (self, chat);
2858 /* Don't use tpaw_window_present_with_time () which would move the window
2859 * to our current desktop but move to the window's desktop instead. This is
2860 * more coherent with Shell's 'app is ready' notication which moves the view
2861 * to the app desktop rather than moving the app itself. */
2862 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2864 gtk_widget_grab_focus (chat->input_text_view);
2865 return self;
2868 static void
2869 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2870 guint *nb_rooms,
2871 guint *nb_private)
2873 GList *l;
2874 guint _nb_rooms = 0, _nb_private = 0;
2876 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2878 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2879 _nb_rooms++;
2880 else
2881 _nb_private++;
2884 if (nb_rooms != NULL)
2885 *nb_rooms = _nb_rooms;
2886 if (nb_private != NULL)
2887 *nb_private = _nb_private;
2890 EmpathyIndividualManager *
2891 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2893 return self->priv->individual_mgr;