Updated Portuguese translation
[empathy-mirror.git] / src / empathy-chat-window.c
blobba23b74177a4a529a1419108e9e93fe991db1b63
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 GtkStyleContext *style_context;
384 PangoContext *pango_context;
385 PangoFontDescription *font_desc;
386 PangoFontMetrics *metrics;
388 button = g_object_get_data (G_OBJECT (user_data),
389 "chat-window-tab-close-button");
390 style_context = gtk_widget_get_style_context (hbox);
391 pango_context = gtk_widget_get_pango_context (hbox);
393 gtk_style_context_save (style_context);
394 gtk_style_context_set_state (style_context, GTK_STATE_FLAG_NORMAL);
395 gtk_style_context_get (style_context,
396 GTK_STATE_FLAG_NORMAL,
397 "font", &font_desc,
398 NULL);
399 gtk_style_context_restore (style_context);
401 metrics = pango_context_get_metrics (pango_context, font_desc,
402 pango_context_get_language (pango_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);
415 pango_font_description_free (font_desc);
418 static GtkWidget *
419 create_close_button (void)
421 GtkWidget *button, *image;
422 GtkStyleContext *context;
424 button = gtk_button_new ();
426 context = gtk_widget_get_style_context (button);
427 gtk_style_context_add_class (context, "empathy-tab-close-button");
429 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
430 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
432 /* We don't want focus/keynav for the button to avoid clutter, and
433 * Ctrl-W works anyway.
435 gtk_widget_set_can_focus (button, FALSE);
436 gtk_widget_set_can_default (button, FALSE);
438 image = gtk_image_new_from_icon_name ("window-close-symbolic",
439 GTK_ICON_SIZE_MENU);
440 gtk_widget_show (image);
442 gtk_container_add (GTK_CONTAINER (button), image);
444 return button;
447 static GtkWidget *
448 chat_window_create_label (EmpathyChatWindow *window,
449 EmpathyChat *chat,
450 gboolean is_tab_label)
452 GtkWidget *hbox;
453 GtkWidget *name_label;
454 GtkWidget *status_image;
455 GtkWidget *event_box;
456 GtkWidget *event_box_hbox;
457 PangoAttrList *attr_list;
458 PangoAttribute *attr;
460 /* The spacing between the button and the label. */
461 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
463 event_box = gtk_event_box_new ();
464 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
466 name_label = gtk_label_new (NULL);
467 if (is_tab_label)
468 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
470 attr_list = pango_attr_list_new ();
471 attr = pango_attr_scale_new (1/1.2);
472 attr->start_index = 0;
473 attr->end_index = -1;
474 pango_attr_list_insert (attr_list, attr);
475 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
476 pango_attr_list_unref (attr_list);
478 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
479 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
480 g_object_set_data (G_OBJECT (chat),
481 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
482 name_label);
484 status_image = gtk_image_new ();
486 /* Spacing between the icon and label. */
487 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
489 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
490 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
492 g_object_set_data (G_OBJECT (chat),
493 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
494 status_image);
495 g_object_set_data (G_OBJECT (chat),
496 is_tab_label ? "chat-window-tab-tooltip-widget" :
497 "chat-window-menu-tooltip-widget",
498 event_box);
500 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
501 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
503 if (is_tab_label)
505 GtkWidget *close_button;
506 GtkWidget *sending_spinner;
508 sending_spinner = gtk_spinner_new ();
510 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
511 FALSE, FALSE, 0);
512 g_object_set_data (G_OBJECT (chat),
513 "chat-window-tab-sending-spinner",
514 sending_spinner);
516 close_button = create_close_button ();
517 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
518 close_button);
520 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
522 g_signal_connect (close_button,
523 "clicked",
524 G_CALLBACK (chat_window_close_clicked_cb), chat);
526 /* React to theme changes and also setup the size correctly. */
527 g_signal_connect (hbox, "style-updated",
528 G_CALLBACK (chat_tab_style_updated_cb), chat);
531 gtk_widget_show_all (hbox);
533 return hbox;
536 static void
537 _submenu_notify_visible_changed_cb (GObject *object,
538 GParamSpec *pspec,
539 gpointer userdata)
541 g_signal_handlers_disconnect_by_func (object,
542 _submenu_notify_visible_changed_cb, userdata);
544 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
547 static void
548 chat_window_menu_context_update (EmpathyChatWindow *self,
549 gint num_pages)
551 gboolean first_page;
552 gboolean last_page;
553 gboolean wrap_around;
554 gboolean is_connected;
555 gint page_num;
557 page_num = gtk_notebook_get_current_page (
558 GTK_NOTEBOOK (self->priv->notebook));
559 first_page = (page_num == 0);
560 last_page = (page_num == (num_pages - 1));
561 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
562 &wrap_around, NULL);
563 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
565 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
566 wrap_around));
567 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
568 wrap_around));
569 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
570 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
571 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
572 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
575 static void
576 chat_window_conversation_menu_update (EmpathyChatWindow *self)
578 EmpathyTpChat *tp_chat;
579 TpConnection *connection;
580 GtkAction *action;
581 gboolean sensitive = FALSE;
583 g_return_if_fail (self->priv->current_chat != NULL);
585 action = gtk_ui_manager_get_action (self->priv->ui_manager,
586 "/chats_menubar/menu_conv/menu_conv_invite_participant");
587 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
589 if (tp_chat != NULL)
591 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
593 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
594 (tp_connection_get_status (connection, NULL) ==
595 TP_CONNECTION_STATUS_CONNECTED);
598 gtk_action_set_sensitive (action, sensitive);
601 static void
602 chat_window_contact_menu_update (EmpathyChatWindow *self)
604 GtkWidget *menu, *submenu, *orig_submenu;
606 if (self->priv->updating_menu)
607 return;
608 self->priv->updating_menu = TRUE;
610 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
611 "/chats_menubar/menu_contact");
612 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
614 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
616 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
618 if (submenu != NULL)
620 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
621 g_object_set_data (G_OBJECT (submenu), "window", self);
623 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
624 gtk_widget_show (menu);
625 gtk_widget_set_sensitive (menu, TRUE);
627 else
629 gtk_widget_set_sensitive (menu, FALSE);
632 else
634 tp_g_signal_connect_object (orig_submenu,
635 "notify::visible",
636 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
639 self->priv->updating_menu = FALSE;
642 static guint
643 get_all_unread_messages (EmpathyChatWindow *self)
645 GList *l;
646 guint nb = 0;
648 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
649 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
651 return nb;
654 static gchar *
655 get_window_title_name (EmpathyChatWindow *self)
657 gchar *active_name, *ret;
658 guint nb_chats;
659 guint current_unread_msgs;
661 nb_chats = g_list_length (self->priv->chats);
662 g_assert (nb_chats > 0);
664 active_name = empathy_chat_dup_name (self->priv->current_chat);
666 current_unread_msgs = empathy_chat_get_nb_unread_messages (
667 self->priv->current_chat);
669 if (nb_chats == 1)
671 /* only one tab */
672 if (current_unread_msgs == 0)
673 ret = g_strdup (active_name);
674 else
675 ret = g_strdup_printf (ngettext (
676 "%s (%d unread)",
677 "%s (%d unread)", current_unread_msgs),
678 active_name, current_unread_msgs);
680 else
682 guint nb_others = nb_chats - 1;
683 guint all_unread_msgs;
685 all_unread_msgs = get_all_unread_messages (self);
687 if (all_unread_msgs == 0)
689 /* no unread message */
690 ret = g_strdup_printf (ngettext (
691 "%s (and %u other)",
692 "%s (and %u others)", nb_others),
693 active_name, nb_others);
695 else if (all_unread_msgs == current_unread_msgs)
697 /* unread messages are in the current tab */
698 ret = g_strdup_printf (ngettext (
699 "%s (%d unread)",
700 "%s (%d unread)", current_unread_msgs),
701 active_name, current_unread_msgs);
703 else if (current_unread_msgs == 0)
705 /* unread messages are in other tabs */
706 ret = g_strdup_printf (ngettext (
707 "%s (%d unread from others)",
708 "%s (%d unread from others)",
709 all_unread_msgs),
710 active_name, all_unread_msgs);
712 else
714 /* unread messages are in all the tabs */
715 ret = g_strdup_printf (ngettext (
716 "%s (%d unread from all)",
717 "%s (%d unread from all)",
718 all_unread_msgs),
719 active_name, all_unread_msgs);
723 g_free (active_name);
725 return ret;
728 static void
729 chat_window_title_update (EmpathyChatWindow *self)
731 gchar *name;
733 name = get_window_title_name (self);
734 gtk_window_set_title (GTK_WINDOW (self), name);
735 g_free (name);
738 static void
739 chat_window_icon_update (EmpathyChatWindow *self,
740 gboolean new_messages)
742 GdkPixbuf *icon;
743 EmpathyContact *remote_contact;
744 gboolean avatar_in_icon;
745 guint n_chats;
747 n_chats = g_list_length (self->priv->chats);
749 /* Update window icon */
750 if (new_messages)
752 gtk_window_set_icon_name (GTK_WINDOW (self),
753 EMPATHY_IMAGE_MESSAGE);
755 else
757 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
758 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
760 if (n_chats == 1 && avatar_in_icon)
762 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
763 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
764 0, 0);
765 gtk_window_set_icon (GTK_WINDOW (self), icon);
767 if (icon != NULL)
768 g_object_unref (icon);
770 else
772 gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
777 static void
778 chat_window_close_button_update (EmpathyChatWindow *self,
779 gint num_pages)
781 GtkWidget *chat;
782 GtkWidget *chat_close_button;
783 gint i;
785 if (num_pages == 1)
787 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
788 chat_close_button = g_object_get_data (G_OBJECT (chat),
789 "chat-window-tab-close-button");
790 gtk_widget_hide (chat_close_button);
792 else
794 for (i=0; i<num_pages; i++)
796 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
797 chat_close_button = g_object_get_data (G_OBJECT (chat),
798 "chat-window-tab-close-button");
799 gtk_widget_show (chat_close_button);
804 static void
805 chat_window_update (EmpathyChatWindow *self,
806 gboolean update_contact_menu)
808 gint num_pages;
810 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
812 /* Update Tab menu */
813 chat_window_menu_context_update (self, num_pages);
815 chat_window_conversation_menu_update (self);
817 /* If this update is due to a focus-in event, we know the menu will be
818 the same as when we last left it, so no work to do. Besides, if we
819 swap out the menu on a focus-in, we may confuse any external global
820 menu watching. */
821 if (update_contact_menu)
823 chat_window_contact_menu_update (self);
826 chat_window_title_update (self);
828 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
830 chat_window_close_button_update (self, num_pages);
833 static void
834 append_markup_printf (GString *string,
835 const char *format,
836 ...)
838 gchar *tmp;
839 va_list args;
841 va_start (args, format);
843 tmp = g_markup_vprintf_escaped (format, args);
844 g_string_append (string, tmp);
845 g_free (tmp);
847 va_end (args);
850 static void
851 chat_window_update_chat_tab_full (EmpathyChat *chat,
852 gboolean update_contact_menu)
854 EmpathyChatWindow *self;
855 EmpathyContact *remote_contact;
856 gchar *name;
857 const gchar *id;
858 TpAccount *account;
859 const gchar *subject;
860 const gchar *status = NULL;
861 GtkWidget *widget;
862 GString *tooltip;
863 gchar *markup;
864 const gchar *icon_name;
865 GtkWidget *tab_image;
866 GtkWidget *menu_image;
867 GtkWidget *sending_spinner;
868 guint nb_sending;
870 self = chat_window_find_chat (chat);
871 if (!self)
872 return;
874 /* Get information */
875 name = empathy_chat_dup_name (chat);
876 account = empathy_chat_get_account (chat);
877 subject = empathy_chat_get_subject (chat);
878 remote_contact = empathy_chat_get_remote_contact (chat);
880 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
881 "remote_contact=%p",
882 name, tp_proxy_get_object_path (account), subject, remote_contact);
884 /* Update tab image */
885 if (empathy_chat_get_tp_chat (chat) == NULL)
887 /* No TpChat, we are disconnected */
888 icon_name = NULL;
890 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
892 icon_name = EMPATHY_IMAGE_MESSAGE;
894 else if (remote_contact && empathy_chat_is_composing (chat))
896 icon_name = EMPATHY_IMAGE_TYPING;
898 else if (empathy_chat_is_sms_channel (chat))
900 icon_name = EMPATHY_IMAGE_SMS;
902 else if (remote_contact)
904 icon_name = empathy_icon_name_for_contact (remote_contact);
906 else
908 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
911 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
912 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
914 if (icon_name != NULL)
916 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
917 GTK_ICON_SIZE_MENU);
918 gtk_widget_show (tab_image);
919 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
920 GTK_ICON_SIZE_MENU);
921 gtk_widget_show (menu_image);
923 else
925 gtk_widget_hide (tab_image);
926 gtk_widget_hide (menu_image);
929 /* Update the sending spinner */
930 nb_sending = empathy_chat_get_n_messages_sending (chat);
931 sending_spinner = g_object_get_data (G_OBJECT (chat),
932 "chat-window-tab-sending-spinner");
934 g_object_set (sending_spinner,
935 "active", nb_sending > 0,
936 "visible", nb_sending > 0,
937 NULL);
939 /* Update tab tooltip */
940 tooltip = g_string_new (NULL);
942 if (remote_contact)
944 id = empathy_contact_get_id (remote_contact);
945 status = empathy_contact_get_presence_message (remote_contact);
947 else
949 id = name;
952 if (empathy_chat_is_sms_channel (chat))
953 append_markup_printf (tooltip, "%s ", _("SMS:"));
955 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
956 id, tp_account_get_display_name (account));
958 if (nb_sending > 0)
960 char *tmp = g_strdup_printf (
961 ngettext ("Sending %d message",
962 "Sending %d messages",
963 nb_sending),
964 nb_sending);
966 g_string_append (tooltip, "\n");
967 g_string_append (tooltip, tmp);
969 gtk_widget_set_tooltip_text (sending_spinner, tmp);
970 g_free (tmp);
973 if (!TPAW_STR_EMPTY (status))
974 append_markup_printf (tooltip, "\n<i>%s</i>", status);
976 if (!TPAW_STR_EMPTY (subject))
977 append_markup_printf (tooltip, "\n<b>%s</b> %s",
978 _("Topic:"), subject);
980 if (remote_contact && empathy_chat_is_composing (chat))
981 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
983 if (remote_contact != NULL)
985 const gchar * const *types;
987 types = empathy_contact_get_client_types (remote_contact);
988 if (empathy_client_types_contains_mobile_device ((GStrv) types))
990 /* I'm on a mobile device ! */
991 gchar *tmp = name;
993 name = g_strdup_printf ("☎ %s", name);
994 g_free (tmp);
998 markup = g_string_free (tooltip, FALSE);
999 widget = g_object_get_data (G_OBJECT (chat),
1000 "chat-window-tab-tooltip-widget");
1001 gtk_widget_set_tooltip_markup (widget, markup);
1003 widget = g_object_get_data (G_OBJECT (chat),
1004 "chat-window-menu-tooltip-widget");
1005 gtk_widget_set_tooltip_markup (widget, markup);
1006 g_free (markup);
1008 /* Update tab and menu label */
1009 if (empathy_chat_is_highlighted (chat))
1011 markup = g_markup_printf_escaped (
1012 "<span color=\"red\" weight=\"bold\">%s</span>",
1013 name);
1015 else
1017 markup = g_markup_escape_text (name, -1);
1020 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1021 gtk_label_set_markup (GTK_LABEL (widget), markup);
1022 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1023 gtk_label_set_markup (GTK_LABEL (widget), markup);
1024 g_free (markup);
1026 /* Update the window if it's the current chat */
1027 if (self->priv->current_chat == chat)
1028 chat_window_update (self, update_contact_menu);
1030 g_free (name);
1033 static void
1034 chat_window_update_chat_tab (EmpathyChat *chat)
1036 chat_window_update_chat_tab_full (chat, TRUE);
1039 static void
1040 chat_window_chat_notify_cb (EmpathyChat *chat)
1042 EmpathyChatWindow *window;
1043 EmpathyContact *old_remote_contact;
1044 EmpathyContact *remote_contact = NULL;
1046 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1047 "chat-window-remote-contact");
1048 remote_contact = empathy_chat_get_remote_contact (chat);
1050 if (old_remote_contact != remote_contact)
1052 /* The remote-contact associated with the chat changed, we need
1053 * to keep track of any change of that contact and update the
1054 * window each time. */
1055 if (remote_contact)
1056 g_signal_connect_swapped (remote_contact, "notify",
1057 G_CALLBACK (chat_window_update_chat_tab), chat);
1059 if (old_remote_contact)
1060 g_signal_handlers_disconnect_by_func (old_remote_contact,
1061 chat_window_update_chat_tab, chat);
1063 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1064 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1067 chat_window_update_chat_tab (chat);
1069 window = chat_window_find_chat (chat);
1070 if (window != NULL)
1071 chat_window_update (window, FALSE);
1074 static void
1075 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1076 EmpathySmiley *smiley,
1077 gpointer user_data)
1079 EmpathyChatWindow *self = user_data;
1080 EmpathyChat *chat;
1081 GtkTextBuffer *buffer;
1083 chat = self->priv->current_chat;
1084 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1086 empathy_chat_insert_smiley (buffer, smiley);
1089 static void
1090 chat_window_conv_activate_cb (GtkAction *action,
1091 EmpathyChatWindow *self)
1093 gboolean is_room;
1094 gboolean active;
1095 EmpathyContact *remote_contact = NULL;
1096 gboolean disconnected;
1098 /* Favorite room menu */
1099 is_room = empathy_chat_is_room (self->priv->current_chat);
1100 if (is_room)
1102 const gchar *room;
1103 TpAccount *account;
1104 gboolean found = FALSE;
1105 EmpathyChatroom *chatroom;
1107 room = empathy_chat_get_id (self->priv->current_chat);
1108 account = empathy_chat_get_account (self->priv->current_chat);
1109 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1110 account, room);
1112 if (chatroom != NULL)
1113 found = empathy_chatroom_is_favorite (chatroom);
1115 DEBUG ("This room %s favorite", found ? "is" : "is not");
1116 gtk_toggle_action_set_active (
1117 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1119 if (chatroom != NULL)
1120 found = empathy_chatroom_is_always_urgent (chatroom);
1122 gtk_toggle_action_set_active (
1123 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1126 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1127 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1129 /* Show contacts menu */
1130 g_object_get (self->priv->current_chat,
1131 "remote-contact", &remote_contact,
1132 "show-contacts", &active,
1133 NULL);
1135 if (remote_contact == NULL)
1137 gtk_toggle_action_set_active (
1138 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1141 /* Menu-items to be visible for MUCs only */
1142 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1143 (remote_contact == NULL));
1145 disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1146 if (disconnected)
1148 gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1149 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1151 else
1153 TpChannel *channel = NULL;
1154 TpContact *self_contact = NULL;
1155 TpHandle self_handle = 0;
1157 channel = (TpChannel *) (empathy_chat_get_tp_chat (
1158 self->priv->current_chat));
1159 self_contact = tp_channel_group_get_self_contact (channel);
1160 if (self_contact == NULL)
1162 /* The channel may not be a group */
1163 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1165 else
1167 self_handle = tp_contact_get_handle (self_contact);
1168 /* There is sometimes a lag between the members-changed signal
1169 emitted on tp-chat and invalidated signal being emitted on the channel.
1170 Leave Chat menu-item should be sensitive only till our self-handle is
1171 a part of channel-members */
1172 gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1173 self_handle != 0);
1176 /* Join Chat is insensitive for a connected chat */
1177 gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1180 if (remote_contact != NULL)
1181 g_object_unref (remote_contact);
1184 static void
1185 chat_window_clear_activate_cb (GtkAction *action,
1186 EmpathyChatWindow *self)
1188 empathy_chat_clear (self->priv->current_chat);
1191 static void
1192 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1193 EmpathyChatWindow *self)
1195 gboolean active;
1196 TpAccount *account;
1197 gchar *name;
1198 const gchar *room;
1199 EmpathyChatroom *chatroom;
1201 active = gtk_toggle_action_get_active (toggle_action);
1202 account = empathy_chat_get_account (self->priv->current_chat);
1203 room = empathy_chat_get_id (self->priv->current_chat);
1204 name = empathy_chat_dup_name (self->priv->current_chat);
1206 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1207 account, room, name);
1209 empathy_chatroom_set_favorite (chatroom, active);
1210 g_object_unref (chatroom);
1211 g_free (name);
1214 static void
1215 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1216 EmpathyChatWindow *self)
1218 gboolean active;
1219 TpAccount *account;
1220 gchar *name;
1221 const gchar *room;
1222 EmpathyChatroom *chatroom;
1224 active = gtk_toggle_action_get_active (toggle_action);
1225 account = empathy_chat_get_account (self->priv->current_chat);
1226 room = empathy_chat_get_id (self->priv->current_chat);
1227 name = empathy_chat_dup_name (self->priv->current_chat);
1229 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1230 account, room, name);
1232 empathy_chatroom_set_always_urgent (chatroom, active);
1233 g_object_unref (chatroom);
1234 g_free (name);
1237 static void
1238 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1239 EmpathyChatWindow *self)
1241 gboolean active;
1243 active = gtk_toggle_action_get_active (toggle_action);
1245 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1248 static void
1249 chat_window_invite_participant_activate_cb (GtkAction *action,
1250 EmpathyChatWindow *self)
1252 GtkWidget *dialog;
1253 EmpathyTpChat *tp_chat;
1254 int response;
1256 g_return_if_fail (self->priv->current_chat != NULL);
1258 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1260 dialog = empathy_invite_participant_dialog_new (
1261 GTK_WINDOW (self), tp_chat);
1263 gtk_widget_show (dialog);
1265 response = gtk_dialog_run (GTK_DIALOG (dialog));
1267 if (response == GTK_RESPONSE_ACCEPT)
1269 TpContact *tp_contact;
1270 EmpathyContact *contact;
1272 tp_contact = empathy_invite_participant_dialog_get_selected (
1273 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1274 if (tp_contact == NULL)
1275 goto out;
1277 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1279 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1281 g_object_unref (contact);
1284 out:
1285 gtk_widget_destroy (dialog);
1288 static void
1289 chat_window_join_chat_activate_cb (GtkAction *action,
1290 EmpathyChatWindow *self)
1292 g_return_if_fail (self->priv->current_chat != NULL);
1294 empathy_chat_join_muc (self->priv->current_chat,
1295 empathy_chat_get_id (self->priv->current_chat));
1298 static void
1299 chat_window_leave_chat_activate_cb (GtkAction *action,
1300 EmpathyChatWindow *self)
1302 EmpathyTpChat * tp_chat;
1304 g_return_if_fail (self->priv->current_chat != NULL);
1306 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1307 if (tp_chat != NULL)
1308 empathy_tp_chat_leave (tp_chat, "");
1311 static void
1312 chat_window_close_activate_cb (GtkAction *action,
1313 EmpathyChatWindow *self)
1315 g_return_if_fail (self->priv->current_chat != NULL);
1317 maybe_close_chat (self, self->priv->current_chat);
1320 static void
1321 chat_window_edit_activate_cb (GtkAction *action,
1322 EmpathyChatWindow *self)
1324 GtkClipboard *clipboard;
1325 GtkTextBuffer *buffer;
1326 gboolean text_available;
1328 g_return_if_fail (self->priv->current_chat != NULL);
1330 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1332 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1333 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1334 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1335 return;
1338 buffer = gtk_text_view_get_buffer (
1339 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1341 if (gtk_text_buffer_get_has_selection (buffer))
1343 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1344 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1346 else
1348 gboolean selection;
1350 selection = empathy_theme_adium_get_has_selection (
1351 self->priv->current_chat->view);
1353 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1354 gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1357 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1358 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1359 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1362 static void
1363 chat_window_cut_activate_cb (GtkAction *action,
1364 EmpathyChatWindow *self)
1366 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1368 empathy_chat_cut (self->priv->current_chat);
1371 static void
1372 chat_window_copy_activate_cb (GtkAction *action,
1373 EmpathyChatWindow *self)
1375 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1377 empathy_chat_copy (self->priv->current_chat);
1380 static void
1381 chat_window_paste_activate_cb (GtkAction *action,
1382 EmpathyChatWindow *self)
1384 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1386 empathy_chat_paste (self->priv->current_chat);
1389 static void
1390 chat_window_find_activate_cb (GtkAction *action,
1391 EmpathyChatWindow *self)
1393 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1395 empathy_chat_find (self->priv->current_chat);
1398 static void
1399 chat_window_tabs_next_activate_cb (GtkAction *action,
1400 EmpathyChatWindow *self)
1402 gint index_, numPages;
1403 gboolean wrap_around;
1405 g_object_get (gtk_settings_get_default (),
1406 "gtk-keynav-wrap-around", &wrap_around,
1407 NULL);
1409 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1410 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1412 if (index_ == (numPages - 1) && wrap_around)
1414 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1415 return;
1418 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1421 static void
1422 chat_window_tabs_previous_activate_cb (GtkAction *action,
1423 EmpathyChatWindow *self)
1425 gint index_, numPages;
1426 gboolean wrap_around;
1428 g_object_get (gtk_settings_get_default (),
1429 "gtk-keynav-wrap-around", &wrap_around,
1430 NULL);
1432 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1433 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1435 if (index_ <= 0 && wrap_around)
1437 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1438 numPages - 1);
1439 return;
1442 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1445 static void
1446 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1447 EmpathyChatWindow *self)
1449 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1450 empathy_get_current_action_time ());
1453 static void
1454 chat_window_tabs_left_activate_cb (GtkAction *action,
1455 EmpathyChatWindow *self)
1457 EmpathyChat *chat;
1458 gint index_, num_pages;
1460 chat = self->priv->current_chat;
1461 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1462 if (index_ <= 0)
1463 return;
1465 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1466 index_ - 1);
1468 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1469 chat_window_menu_context_update (self, num_pages);
1472 static void
1473 chat_window_tabs_right_activate_cb (GtkAction *action,
1474 EmpathyChatWindow *self)
1476 EmpathyChat *chat;
1477 gint index_, num_pages;
1479 chat = self->priv->current_chat;
1480 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1482 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1483 index_ + 1);
1485 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1486 chat_window_menu_context_update (self, num_pages);
1489 static EmpathyChatWindow *
1490 empathy_chat_window_new (void)
1492 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1493 "default-width", 580,
1494 "default-height", 480,
1495 "title", _("Chat"),
1496 "role", "chat",
1497 NULL);
1500 static void
1501 chat_window_detach_activate_cb (GtkAction *action,
1502 EmpathyChatWindow *self)
1504 EmpathyChatWindow *new_window;
1505 EmpathyChat *chat;
1507 chat = self->priv->current_chat;
1508 new_window = empathy_chat_window_new ();
1510 empathy_chat_window_move_chat (self, new_window, chat);
1512 gtk_widget_show (GTK_WIDGET (new_window));
1515 static void
1516 chat_window_help_contents_activate_cb (GtkAction *action,
1517 EmpathyChatWindow *self)
1519 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1522 static void
1523 chat_window_help_about_activate_cb (GtkAction *action,
1524 EmpathyChatWindow *self)
1526 empathy_about_dialog_new (GTK_WINDOW (self));
1529 static gboolean
1530 chat_window_delete_event_cb (GtkWidget *dialog,
1531 GdkEvent *event,
1532 EmpathyChatWindow *self)
1534 EmpathyChat *chat = NULL;
1535 guint n_rooms = 0;
1536 GList *l;
1538 DEBUG ("Delete event received");
1540 for (l = self->priv->chats; l != NULL; l = l->next)
1542 if (chat_needs_close_confirmation (l->data))
1544 chat = l->data;
1545 n_rooms++;
1549 if (n_rooms > 0)
1551 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1553 else
1555 remove_all_chats (self);
1558 return TRUE;
1561 static void
1562 chat_window_composing_cb (EmpathyChat *chat,
1563 gboolean is_composing,
1564 EmpathyChatWindow *self)
1566 chat_window_update_chat_tab (chat);
1569 static void
1570 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1571 gboolean urgent)
1573 gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1576 static void
1577 chat_window_notification_closed_cb (NotifyNotification *notify,
1578 EmpathyChatWindow *self)
1580 g_object_unref (notify);
1581 if (self->priv->notification == notify)
1582 self->priv->notification = NULL;
1585 static void
1586 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1587 EmpathyMessage *message,
1588 EmpathyChat *chat)
1590 EmpathyContact *sender;
1591 const gchar *header;
1592 char *escaped;
1593 const char *body;
1594 GdkPixbuf *pixbuf;
1595 gboolean res, has_x_canonical_append;
1596 NotifyNotification *notification = self->priv->notification;
1598 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1599 return;
1601 res = g_settings_get_boolean (self->priv->gsettings_notif,
1602 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1604 if (!res)
1605 return;
1607 sender = empathy_message_get_sender (message);
1608 header = empathy_contact_get_alias (sender);
1609 body = empathy_message_get_body (message);
1610 escaped = g_markup_escape_text (body, -1);
1612 has_x_canonical_append = empathy_notify_manager_has_capability (
1613 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1615 if (notification != NULL && !has_x_canonical_append)
1617 /* if the notification server supports x-canonical-append, it is
1618 better to not use notify_notification_update to avoid
1619 overwriting the current notification message */
1620 notify_notification_update (notification,
1621 header, escaped, NULL);
1623 else
1625 /* if the notification server supports x-canonical-append,
1626 the hint will be added, so that the message from the
1627 just created notification will be automatically appended
1628 to an existing notification with the same title.
1629 In this way the previous message will not be lost: the new
1630 message will appear below it, in the same notification */
1631 const gchar *category = empathy_chat_is_room (chat)
1632 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1633 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1635 notification = empathy_notify_manager_create_notification (header,
1636 escaped, NULL);
1638 if (self->priv->notification == NULL)
1639 self->priv->notification = notification;
1641 tp_g_signal_connect_object (notification, "closed",
1642 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1644 if (has_x_canonical_append)
1646 /* We have to set a not empty string to keep libnotify happy */
1647 notify_notification_set_hint_string (notification,
1648 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1651 notify_notification_set_hint (notification,
1652 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1655 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1656 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1658 if (pixbuf != NULL)
1660 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1661 g_object_unref (pixbuf);
1664 notify_notification_show (notification, NULL);
1666 g_free (escaped);
1669 static gboolean
1670 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1672 gboolean has_focus;
1674 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1676 g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1678 return has_focus;
1681 static void
1682 chat_window_new_message_cb (EmpathyChat *chat,
1683 EmpathyMessage *message,
1684 gboolean pending,
1685 gboolean should_highlight,
1686 EmpathyChatWindow *self)
1688 gboolean has_focus;
1689 gboolean needs_urgency;
1690 EmpathyContact *sender;
1692 has_focus = empathy_chat_window_has_focus (self);
1694 /* - if we're the sender, we play the sound if it's specified in the
1695 * preferences and we're not away.
1696 * - if we receive a message, we play the sound if it's specified in the
1697 * preferences and the window does not have focus on the chat receiving
1698 * the message.
1701 sender = empathy_message_get_sender (message);
1703 if (empathy_contact_is_user (sender))
1705 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1706 EMPATHY_SOUND_MESSAGE_OUTGOING);
1707 return;
1710 if (has_focus && self->priv->current_chat == chat)
1712 /* window and tab are focused so consider the message to be read */
1714 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1715 empathy_chat_messages_read (chat);
1716 return;
1719 /* Update the chat tab if this is the first unread message */
1720 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1722 chat_window_update_chat_tab (chat);
1725 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1726 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1727 * an unamed MUC (msn-like).
1728 * In case of a MUC, we set urgency if either:
1729 * a) the chatroom's always_urgent property is TRUE
1730 * b) the message contains our alias
1732 if (empathy_chat_is_room (chat))
1734 TpAccount *account;
1735 const gchar *room;
1736 EmpathyChatroom *chatroom;
1738 account = empathy_chat_get_account (chat);
1739 room = empathy_chat_get_id (chat);
1741 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1742 account, room);
1744 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1745 needs_urgency = TRUE;
1746 else
1747 needs_urgency = should_highlight;
1749 else
1751 needs_urgency = TRUE;
1754 if (needs_urgency)
1756 if (!has_focus)
1757 chat_window_set_urgency_hint (self, TRUE);
1759 /* Pending messages have already been displayed and notified in the
1760 * approver, so we don't display a notification and play a sound
1761 * for those */
1762 if (!pending)
1764 empathy_sound_manager_play (self->priv->sound_mgr,
1765 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1767 chat_window_show_or_update_notification (self, message, chat);
1771 /* update the number of unread messages and the window icon */
1772 chat_window_title_update (self);
1773 chat_window_icon_update (self, TRUE);
1776 static void
1777 chat_window_command_part (EmpathyChat *chat,
1778 GStrv strv)
1780 EmpathyChat *chat_to_be_parted;
1781 EmpathyTpChat *tp_chat = NULL;
1783 if (strv[1] == NULL)
1785 /* No chatroom ID specified */
1786 tp_chat = empathy_chat_get_tp_chat (chat);
1788 if (tp_chat)
1789 empathy_tp_chat_leave (tp_chat, "");
1791 return;
1794 chat_to_be_parted = empathy_chat_window_find_chat (
1795 empathy_chat_get_account (chat), strv[1], FALSE);
1797 if (chat_to_be_parted != NULL)
1799 /* Found a chatroom matching the specified ID */
1800 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1802 if (tp_chat)
1803 empathy_tp_chat_leave (tp_chat, strv[2]);
1805 else
1807 gchar *message;
1809 /* Going by the syntax of PART command:
1811 * /PART [<chatroom-ID>] [<reason>]
1813 * Chatroom-ID is not a must to specify a reason.
1814 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1815 * MUC then the current chatroom should be parted and srtv[1] should
1816 * be treated as part of the optional part-message. */
1817 message = g_strconcat (strv[1], " ", strv[2], NULL);
1818 tp_chat = empathy_chat_get_tp_chat (chat);
1820 if (tp_chat)
1821 empathy_tp_chat_leave (tp_chat, message);
1823 g_free (message);
1827 static GtkNotebook *
1828 notebook_create_window_cb (GtkNotebook *source,
1829 GtkWidget *page,
1830 gint x,
1831 gint y,
1832 gpointer user_data)
1834 EmpathyChatWindow *window, *new_window;
1835 EmpathyChat *chat;
1837 chat = EMPATHY_CHAT (page);
1838 window = chat_window_find_chat (chat);
1840 new_window = empathy_chat_window_new ();
1842 DEBUG ("Detach hook called");
1844 empathy_chat_window_move_chat (window, new_window, chat);
1846 gtk_widget_show (GTK_WIDGET (new_window));
1847 gtk_window_move (GTK_WINDOW (new_window), x, y);
1849 return NULL;
1852 static void
1853 chat_window_page_switched_cb (GtkNotebook *notebook,
1854 GtkWidget *child,
1855 gint page_num,
1856 EmpathyChatWindow *self)
1858 EmpathyChat *chat = EMPATHY_CHAT (child);
1860 DEBUG ("Page switched");
1862 if (self->priv->page_added)
1864 self->priv->page_added = FALSE;
1865 empathy_chat_scroll_down (chat);
1867 else if (self->priv->current_chat == chat)
1869 return;
1872 self->priv->current_chat = chat;
1873 empathy_chat_messages_read (chat);
1875 chat_window_update_chat_tab (chat);
1878 static void
1879 chat_window_page_added_cb (GtkNotebook *notebook,
1880 GtkWidget *child,
1881 guint page_num,
1882 EmpathyChatWindow *self)
1884 EmpathyChat *chat;
1886 /* If we just received DND to the same window, we don't want
1887 * to do anything here like removing the tab and then readding
1888 * it, so we return here and in "page-added".
1890 if (self->priv->dnd_same_window)
1892 DEBUG ("Page added (back to the same window)");
1893 self->priv->dnd_same_window = FALSE;
1894 return;
1897 DEBUG ("Page added");
1899 /* Get chat object */
1900 chat = EMPATHY_CHAT (child);
1902 /* Connect chat signals for this window */
1903 g_signal_connect (chat, "composing",
1904 G_CALLBACK (chat_window_composing_cb), self);
1905 g_signal_connect (chat, "new-message",
1906 G_CALLBACK (chat_window_new_message_cb), self);
1907 g_signal_connect (chat, "part-command-entered",
1908 G_CALLBACK (chat_window_command_part), NULL);
1909 g_signal_connect (chat, "notify::tp-chat",
1910 G_CALLBACK (chat_window_update_chat_tab), self);
1912 /* Set flag so we know to perform some special operations on
1913 * switch page due to the new page being added.
1915 self->priv->page_added = TRUE;
1917 /* Get list of chats up to date */
1918 self->priv->chats = g_list_append (self->priv->chats, chat);
1920 chat_window_update_chat_tab (chat);
1923 static void
1924 chat_window_page_removed_cb (GtkNotebook *notebook,
1925 GtkWidget *child,
1926 guint page_num,
1927 EmpathyChatWindow *self)
1929 EmpathyChat *chat;
1931 /* If we just received DND to the same window, we don't want
1932 * to do anything here like removing the tab and then readding
1933 * it, so we return here and in "page-added".
1935 if (self->priv->dnd_same_window)
1937 DEBUG ("Page removed (and will be readded to same window)");
1938 return;
1941 DEBUG ("Page removed");
1943 /* Get chat object */
1944 chat = EMPATHY_CHAT (child);
1946 /* Disconnect all signal handlers for this chat and this window */
1947 g_signal_handlers_disconnect_by_func (chat,
1948 G_CALLBACK (chat_window_composing_cb), self);
1949 g_signal_handlers_disconnect_by_func (chat,
1950 G_CALLBACK (chat_window_new_message_cb), self);
1951 g_signal_handlers_disconnect_by_func (chat,
1952 G_CALLBACK (chat_window_update_chat_tab), self);
1954 /* Keep list of chats up to date */
1955 self->priv->chats = g_list_remove (self->priv->chats, chat);
1956 empathy_chat_messages_read (chat);
1958 if (self->priv->chats == NULL)
1960 gtk_widget_destroy (GTK_WIDGET (self));
1962 else
1964 chat_window_update (self, TRUE);
1968 static gboolean
1969 chat_window_focus_in_event_cb (GtkWidget *widget,
1970 GdkEvent *event,
1971 EmpathyChatWindow *self)
1973 empathy_chat_messages_read (self->priv->current_chat);
1975 chat_window_set_urgency_hint (self, FALSE);
1977 /* Update the title, since we now mark all unread messages as read. */
1978 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1980 return FALSE;
1983 static void
1984 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1985 EmpathyChatWindow *self)
1987 chat_window_contact_menu_update (self);
1990 static gboolean
1991 chat_window_focus_out_event_cb (GtkWidget *widget,
1992 GdkEvent *event,
1993 EmpathyChatWindow *self)
1995 if (self->priv->individual_mgr != NULL)
1996 return FALSE;
1998 /* Keep the individual manager alive so we won't fetch everything from Folks
1999 * each time we need to use it. Loading FolksAggregator can takes quite a
2000 * while (if user has a huge LDAP abook for example) and it blocks
2001 * the mainloop during most of this loading. We workaround this by loading
2002 * it when the chat window has been unfocused and so, hopefully, not impact
2003 * the reactivity of the chat window too much.
2005 * The individual manager (and so Folks) is needed to know to which
2006 * FolksIndividual a TpContact belongs, including:
2007 * - empathy_chat_get_contact_menu: to list all the personas of the contact
2008 * - empathy_display_individual_info: to invoke gnome-contacts with the
2009 * FolksIndividual.id of the contact
2010 * - drag_data_received_individual_id: to find the individual associated
2011 * with the ID we received from the DnD in order to invite him.
2013 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2015 if (!empathy_individual_manager_get_contacts_loaded (
2016 self->priv->individual_mgr))
2018 /* We want to update the contact menu when Folks is loaded so we can
2019 * list all the personas of the contact. */
2020 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2021 G_CALLBACK (contacts_loaded_cb), self, 0);
2024 g_object_notify (G_OBJECT (self), "individual-manager");
2026 return FALSE;
2029 static gboolean
2030 chat_window_drag_drop (GtkWidget *widget,
2031 GdkDragContext *context,
2032 int x,
2033 int y,
2034 guint time_,
2035 EmpathyChatWindow *self)
2037 GdkAtom target;
2039 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2040 if (target == GDK_NONE)
2041 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2043 if (target != GDK_NONE)
2045 gtk_drag_get_data (widget, context, target, time_);
2046 return TRUE;
2049 return FALSE;
2052 static gboolean
2053 chat_window_drag_motion (GtkWidget *widget,
2054 GdkDragContext *context,
2055 int x,
2056 int y,
2057 guint time_,
2058 EmpathyChatWindow *self)
2060 GdkAtom target;
2062 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2064 if (target != GDK_NONE)
2066 /* This is a file drag. Ensure the contact is online and set the
2067 drag type to COPY. Note that it's possible that the tab will
2068 be switched by GTK+ after a timeout from drag_motion without
2069 getting another drag_motion to disable the drop. You have
2070 to hold your mouse really still.
2072 EmpathyContact *contact;
2074 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2076 /* contact is NULL for multi-user chats. We don't do
2077 * file transfers to MUCs. We also don't send files
2078 * to offline contacts or contacts that don't support
2079 * file transfer.
2081 if ((contact == NULL) || !empathy_contact_is_online (contact))
2083 gdk_drag_status (context, 0, time_);
2084 return FALSE;
2087 if (!(empathy_contact_get_capabilities (contact)
2088 & EMPATHY_CAPABILITIES_FT))
2090 gdk_drag_status (context, 0, time_);
2091 return FALSE;
2094 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2095 return TRUE;
2098 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2099 if (target != GDK_NONE)
2101 /* This is a drag of a contact from a contact list. Set to COPY.
2102 FIXME: If this drag is to a MUC window, it invites the user.
2103 Otherwise, it opens a chat. Should we use a different drag
2104 type for invites? Should we allow ASK?
2106 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2107 return TRUE;
2110 return FALSE;
2113 static void
2114 drag_data_received_individual_id (EmpathyChatWindow *self,
2115 GtkWidget *widget,
2116 GdkDragContext *context,
2117 int x,
2118 int y,
2119 GtkSelectionData *selection,
2120 guint info,
2121 guint time_)
2123 const gchar *id;
2124 FolksIndividual *individual;
2125 EmpathyTpChat *chat;
2126 TpContact *tp_contact;
2127 TpConnection *conn;
2128 EmpathyContact *contact;
2130 id = (const gchar *) gtk_selection_data_get_data (selection);
2132 DEBUG ("DND invididual %s", id);
2134 if (self->priv->current_chat == NULL)
2135 goto out;
2137 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2138 if (chat == NULL)
2139 goto out;
2141 if (!empathy_tp_chat_can_add_contact (chat))
2143 DEBUG ("Can't invite contact to %s",
2144 tp_proxy_get_object_path (chat));
2145 goto out;
2148 if (self->priv->individual_mgr == NULL)
2149 /* Not likely as we have to focus out the chat window in order to start
2150 * the DnD but best to be safe. */
2151 goto out;
2153 individual = empathy_individual_manager_lookup_member (
2154 self->priv->individual_mgr, id);
2155 if (individual == NULL)
2157 DEBUG ("Failed to find individual %s", id);
2158 goto out;
2161 conn = tp_channel_get_connection ((TpChannel *) chat);
2162 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2163 if (tp_contact == NULL)
2165 DEBUG ("Can't find a TpContact on connection %s for %s",
2166 tp_proxy_get_object_path (conn), id);
2167 goto out;
2170 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2171 tp_channel_get_identifier ((TpChannel *) chat));
2173 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2174 empathy_tp_chat_add (chat, contact, NULL);
2175 g_object_unref (contact);
2177 out:
2178 gtk_drag_finish (context, TRUE, FALSE, time_);
2181 static void
2182 chat_window_drag_data_received (GtkWidget *widget,
2183 GdkDragContext *context,
2184 int x,
2185 int y,
2186 GtkSelectionData *selection,
2187 guint info,
2188 guint time_,
2189 EmpathyChatWindow *self)
2191 if (info == DND_DRAG_TYPE_CONTACT_ID)
2193 EmpathyChat *chat = NULL;
2194 EmpathyChatWindow *old_window;
2195 TpAccount *account = NULL;
2196 EmpathyClientFactory *factory;
2197 const gchar *id;
2198 gchar **strv;
2199 const gchar *account_id;
2200 const gchar *contact_id;
2202 id = (const gchar*) gtk_selection_data_get_data (selection);
2204 factory = empathy_client_factory_dup ();
2206 DEBUG ("DND contact from roster with id:'%s'", id);
2208 strv = g_strsplit (id, ":", 2);
2209 if (g_strv_length (strv) == 2)
2211 account_id = strv[0];
2212 contact_id = strv[1];
2214 account = tp_simple_client_factory_ensure_account (
2215 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2217 g_object_unref (factory);
2218 if (account != NULL)
2219 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2222 if (account == NULL)
2224 g_strfreev (strv);
2225 gtk_drag_finish (context, FALSE, FALSE, time_);
2226 return;
2229 if (!chat)
2231 empathy_chat_with_contact_id (account, contact_id,
2232 empathy_get_current_action_time (), NULL, NULL);
2234 g_strfreev (strv);
2235 return;
2238 g_strfreev (strv);
2240 old_window = chat_window_find_chat (chat);
2241 if (old_window)
2243 if (old_window == self)
2245 gtk_drag_finish (context, TRUE, FALSE, time_);
2246 return;
2249 empathy_chat_window_move_chat (old_window, self, chat);
2251 else
2253 empathy_chat_window_add_chat (self, chat);
2256 /* Added to take care of any outstanding chat events */
2257 empathy_chat_window_present_chat (chat,
2258 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2260 /* We should return TRUE to remove the data when doing
2261 * GDK_ACTION_MOVE, but we don't here otherwise it has
2262 * weird consequences, and we handle that internally
2263 * anyway with add_chat () and remove_chat ().
2265 gtk_drag_finish (context, TRUE, FALSE, time_);
2267 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2269 drag_data_received_individual_id (self, widget, context, x, y,
2270 selection, info, time_);
2272 else if (info == DND_DRAG_TYPE_URI_LIST)
2274 EmpathyContact *contact;
2275 const gchar *data;
2277 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2279 /* contact is NULL when current_chat is a multi-user chat.
2280 * We don't do file transfers to MUCs, so just cancel the drag.
2282 if (contact == NULL)
2284 gtk_drag_finish (context, TRUE, FALSE, time_);
2285 return;
2288 data = (const gchar *) gtk_selection_data_get_data (selection);
2289 empathy_send_file_from_uri_list (contact, data);
2291 gtk_drag_finish (context, TRUE, FALSE, time_);
2293 else if (info == DND_DRAG_TYPE_TAB)
2295 EmpathyChat **chat;
2296 EmpathyChatWindow *old_window = NULL;
2298 DEBUG ("DND tab");
2300 chat = (void *) gtk_selection_data_get_data (selection);
2301 old_window = chat_window_find_chat (*chat);
2303 if (old_window)
2305 self->priv->dnd_same_window = (old_window == self);
2307 DEBUG ("DND tab (within same window: %s)",
2308 self->priv->dnd_same_window ? "Yes" : "No");
2311 else
2313 DEBUG ("DND from unknown source");
2314 gtk_drag_finish (context, FALSE, FALSE, time_);
2318 static void
2319 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2320 guint num_chats_in_manager,
2321 EmpathyChatWindow *self)
2323 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2324 num_chats_in_manager > 0);
2327 static void
2328 chat_window_finalize (GObject *object)
2330 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2332 DEBUG ("Finalized: %p", object);
2334 g_object_unref (self->priv->ui_manager);
2335 g_object_unref (self->priv->chatroom_manager);
2336 g_object_unref (self->priv->notify_mgr);
2337 g_object_unref (self->priv->gsettings_chat);
2338 g_object_unref (self->priv->gsettings_notif);
2339 g_object_unref (self->priv->gsettings_ui);
2340 g_object_unref (self->priv->sound_mgr);
2341 g_clear_object (&self->priv->individual_mgr);
2343 if (self->priv->notification != NULL)
2345 notify_notification_close (self->priv->notification, NULL);
2346 self->priv->notification = NULL;
2349 if (self->priv->contact_targets)
2350 gtk_target_list_unref (self->priv->contact_targets);
2352 if (self->priv->file_targets)
2353 gtk_target_list_unref (self->priv->file_targets);
2355 if (self->priv->chat_manager)
2357 g_signal_handler_disconnect (self->priv->chat_manager,
2358 self->priv->chat_manager_chats_changed_id);
2359 g_object_unref (self->priv->chat_manager);
2360 self->priv->chat_manager = NULL;
2363 chat_windows = g_list_remove (chat_windows, self);
2365 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2368 static void
2369 chat_window_get_property (GObject *object,
2370 guint property_id,
2371 GValue *value,
2372 GParamSpec *pspec)
2374 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2376 switch (property_id)
2378 case PROP_INDIVIDUAL_MGR:
2379 g_value_set_object (value, self->priv->individual_mgr);
2380 default:
2381 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2382 break;
2386 static void
2387 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2389 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2390 GParamSpec *spec;
2392 object_class->get_property = chat_window_get_property;
2393 object_class->finalize = chat_window_finalize;
2395 spec = g_param_spec_object ("individual-manager", "individual-manager",
2396 "EmpathyIndividualManager",
2397 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2398 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2399 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2401 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2404 static void
2405 empathy_chat_window_init (EmpathyChatWindow *self)
2407 GtkBuilder *gui;
2408 GtkAccelGroup *accel_group;
2409 GClosure *closure;
2410 GtkWidget *menu;
2411 GtkWidget *submenu;
2412 guint i;
2413 GtkWidget *chat_vbox;
2414 gchar *filename;
2415 EmpathySmileyManager *smiley_manager;
2417 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2418 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2420 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2421 gui = tpaw_builder_get_file (filename,
2422 "chat_vbox", &chat_vbox,
2423 "ui_manager", &self->priv->ui_manager,
2424 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2425 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2426 "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2427 "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2428 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2429 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2430 "menu_edit_cut", &self->priv->menu_edit_cut,
2431 "menu_edit_copy", &self->priv->menu_edit_copy,
2432 "menu_edit_paste", &self->priv->menu_edit_paste,
2433 "menu_edit_find", &self->priv->menu_edit_find,
2434 "menu_tabs_next", &self->priv->menu_tabs_next,
2435 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2436 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2437 "menu_tabs_left", &self->priv->menu_tabs_left,
2438 "menu_tabs_right", &self->priv->menu_tabs_right,
2439 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2440 NULL);
2441 g_free (filename);
2443 tpaw_builder_connect (gui, self,
2444 "menu_conv", "activate", chat_window_conv_activate_cb,
2445 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2446 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2447 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2448 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2449 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2450 "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2451 "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2452 "menu_conv_close", "activate", chat_window_close_activate_cb,
2453 "menu_edit", "activate", chat_window_edit_activate_cb,
2454 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2455 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2456 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2457 "menu_edit_find", "activate", chat_window_find_activate_cb,
2458 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2459 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2460 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2461 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2462 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2463 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2464 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2465 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2466 NULL);
2468 empathy_set_css_provider (GTK_WIDGET (self));
2470 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2471 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2472 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2473 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2475 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2477 self->priv->notebook = gtk_notebook_new ();
2479 g_signal_connect (self->priv->notebook, "create-window",
2480 G_CALLBACK (notebook_create_window_cb), self);
2482 gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2484 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2485 "EmpathyChatWindow");
2486 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2487 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2488 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2489 gtk_widget_show (self->priv->notebook);
2491 /* Set up accels */
2492 accel_group = gtk_accel_group_new ();
2493 gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2495 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2497 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2498 NULL);
2500 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2501 closure);
2504 g_object_unref (accel_group);
2506 /* Set up drag target lists */
2507 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2508 G_N_ELEMENTS (drag_types_dest_contact));
2510 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2511 G_N_ELEMENTS (drag_types_dest_file));
2513 /* Set up smiley menu */
2514 smiley_manager = empathy_smiley_manager_dup_singleton ();
2515 submenu = empathy_smiley_menu_new (smiley_manager,
2516 chat_window_insert_smiley_activate_cb, self);
2518 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2519 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2520 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2521 g_object_unref (smiley_manager);
2523 /* Set up signals we can't do with ui file since we may need to
2524 * block/unblock them at some later stage.
2527 g_signal_connect (self, "delete_event",
2528 G_CALLBACK (chat_window_delete_event_cb), self);
2529 g_signal_connect (self, "focus_in_event",
2530 G_CALLBACK (chat_window_focus_in_event_cb), self);
2531 g_signal_connect (self, "focus_out_event",
2532 G_CALLBACK (chat_window_focus_out_event_cb), self);
2533 g_signal_connect_after (self->priv->notebook, "switch_page",
2534 G_CALLBACK (chat_window_page_switched_cb), self);
2535 g_signal_connect (self->priv->notebook, "page_added",
2536 G_CALLBACK (chat_window_page_added_cb), self);
2537 g_signal_connect (self->priv->notebook, "page_removed",
2538 G_CALLBACK (chat_window_page_removed_cb), self);
2540 /* Set up drag and drop */
2541 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2542 GTK_DEST_DEFAULT_HIGHLIGHT,
2543 drag_types_dest,
2544 G_N_ELEMENTS (drag_types_dest),
2545 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2547 /* connect_after to allow GtkNotebook's built-in tab switching */
2548 g_signal_connect_after (self->priv->notebook, "drag-motion",
2549 G_CALLBACK (chat_window_drag_motion), self);
2550 g_signal_connect (self->priv->notebook, "drag-data-received",
2551 G_CALLBACK (chat_window_drag_data_received), self);
2552 g_signal_connect (self->priv->notebook, "drag-drop",
2553 G_CALLBACK (chat_window_drag_drop), self);
2555 chat_windows = g_list_prepend (chat_windows, self);
2557 /* Set up private details */
2558 self->priv->chats = NULL;
2559 self->priv->current_chat = NULL;
2560 self->priv->notification = NULL;
2562 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2564 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2565 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2566 self->priv->chat_manager, "closed-chats-changed",
2567 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2569 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2570 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2572 g_object_ref (self->priv->ui_manager);
2573 g_object_unref (gui);
2576 /* Returns the window to open a new tab in if there is a suitable window,
2577 * otherwise, returns NULL indicating that a new window should be added.
2579 static EmpathyChatWindow *
2580 empathy_chat_window_get_default (gboolean room)
2582 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2583 GList *l;
2584 gboolean separate_windows = TRUE;
2586 separate_windows = g_settings_get_boolean (gsettings,
2587 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2589 g_object_unref (gsettings);
2591 if (separate_windows)
2592 /* Always create a new window */
2593 return NULL;
2595 for (l = chat_windows; l; l = l->next)
2597 EmpathyChatWindow *chat_window;
2598 guint nb_rooms, nb_private;
2600 chat_window = l->data;
2602 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2604 /* Skip the window if there aren't any rooms in it */
2605 if (room && nb_rooms == 0)
2606 continue;
2608 /* Skip the window if there aren't any 1-1 chats in it */
2609 if (!room && nb_private == 0)
2610 continue;
2612 return chat_window;
2615 return NULL;
2618 static void
2619 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2620 EmpathyChat *chat)
2622 GtkWidget *label;
2623 GtkWidget *popup_label;
2624 GtkWidget *child;
2625 GValue value = { 0, };
2627 g_return_if_fail (self != NULL);
2628 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2630 /* Reference the chat object */
2631 g_object_ref (chat);
2633 /* If this window has just been created, position it */
2634 if (self->priv->chats == NULL)
2636 const gchar *name = "chat-window";
2637 gboolean separate_windows;
2639 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2640 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2642 if (empathy_chat_is_room (chat))
2643 name = "room-window";
2645 if (separate_windows)
2647 gint x, y;
2649 /* Save current position of the window */
2650 gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2652 /* First bind to the 'generic' name. So new window for which we didn't
2653 * save a geometry yet will have the geometry of the last saved
2654 * window (bgo #601191). */
2655 empathy_geometry_bind (GTK_WINDOW (self), name);
2657 /* Restore previous position of the window so the newly created window
2658 * won't be in the same position as the latest saved window and so
2659 * completely hide it. */
2660 gtk_window_move (GTK_WINDOW (self), x, y);
2662 /* Then bind it to the name of the contact/room so we'll save the
2663 * geometry specific to this window */
2664 name = empathy_chat_get_id (chat);
2667 empathy_geometry_bind (GTK_WINDOW (self), name);
2670 child = GTK_WIDGET (chat);
2671 label = chat_window_create_label (self, chat, TRUE);
2672 popup_label = chat_window_create_label (self, chat, FALSE);
2673 gtk_widget_show (child);
2675 g_signal_connect (chat, "notify::name",
2676 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2677 g_signal_connect (chat, "notify::subject",
2678 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2679 g_signal_connect (chat, "notify::remote-contact",
2680 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2681 g_signal_connect (chat, "notify::sms-channel",
2682 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2683 g_signal_connect (chat, "notify::n-messages-sending",
2684 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2685 g_signal_connect (chat, "notify::nb-unread-messages",
2686 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2687 chat_window_chat_notify_cb (chat);
2689 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2690 popup_label);
2691 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2692 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2693 g_value_init (&value, G_TYPE_BOOLEAN);
2694 g_value_set_boolean (&value, TRUE);
2695 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2696 child, "tab-expand" , &value);
2697 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2698 child, "tab-fill" , &value);
2699 g_value_unset (&value);
2701 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2704 static void
2705 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2706 EmpathyChat *chat)
2708 gint position;
2709 EmpathyContact *remote_contact;
2710 EmpathyChatManager *chat_manager;
2712 g_return_if_fail (self != NULL);
2713 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2715 g_signal_handlers_disconnect_by_func (chat,
2716 chat_window_chat_notify_cb, NULL);
2718 remote_contact = g_object_get_data (G_OBJECT (chat),
2719 "chat-window-remote-contact");
2721 if (remote_contact)
2723 g_signal_handlers_disconnect_by_func (remote_contact,
2724 chat_window_update_chat_tab, chat);
2727 chat_manager = empathy_chat_manager_dup_singleton ();
2728 empathy_chat_manager_closed_chat (chat_manager, chat);
2729 g_object_unref (chat_manager);
2731 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2732 GTK_WIDGET (chat));
2733 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2735 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2737 g_object_unref (chat);
2740 static void
2741 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2742 EmpathyChatWindow *new_window,
2743 EmpathyChat *chat)
2745 GtkWidget *widget;
2747 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2748 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2749 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2751 widget = GTK_WIDGET (chat);
2753 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2754 G_OBJECT (widget)->ref_count);
2756 /* We reference here to make sure we don't loose the widget
2757 * and the EmpathyChat object during the move.
2759 g_object_ref (chat);
2760 g_object_ref (widget);
2762 empathy_chat_window_remove_chat (old_window, chat);
2763 empathy_chat_window_add_chat (new_window, chat);
2765 g_object_unref (widget);
2766 g_object_unref (chat);
2769 static void
2770 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2771 EmpathyChat *chat)
2773 gint page_num;
2775 g_return_if_fail (self != NULL);
2776 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2778 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2779 GTK_WIDGET (chat));
2781 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2782 page_num);
2785 EmpathyChat *
2786 empathy_chat_window_find_chat (TpAccount *account,
2787 const gchar *id,
2788 gboolean sms_channel)
2790 GList *l;
2792 g_return_val_if_fail (!TPAW_STR_EMPTY (id), NULL);
2794 for (l = chat_windows; l; l = l->next)
2796 EmpathyChatWindow *window = l->data;
2797 GList *ll;
2799 for (ll = window->priv->chats; ll; ll = ll->next)
2801 EmpathyChat *chat;
2803 chat = ll->data;
2805 if (account == empathy_chat_get_account (chat) &&
2806 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2807 sms_channel == empathy_chat_is_sms_channel (chat))
2808 return chat;
2812 return NULL;
2815 EmpathyChatWindow *
2816 empathy_chat_window_present_chat (EmpathyChat *chat,
2817 gint64 timestamp)
2819 EmpathyChatWindow *self;
2820 guint32 x_timestamp;
2822 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2824 self = chat_window_find_chat (chat);
2826 /* If the chat has no window, create one */
2827 if (self == NULL)
2829 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2830 if (!self)
2832 self = empathy_chat_window_new ();
2834 /* we want to display the newly created window even if we
2835 * don't present it */
2836 gtk_widget_show (GTK_WIDGET (self));
2839 empathy_chat_window_add_chat (self, chat);
2842 /* Don't force the window to show itself when it wasn't
2843 * an action by the user
2845 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2846 return self;
2848 if (x_timestamp != GDK_CURRENT_TIME)
2850 /* Don't present or switch tab if the action was earlier than the
2851 * last actions X time, accounting for overflow and the first ever
2852 * presentation */
2854 if (self->priv->x_user_action_time != 0
2855 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2856 return self;
2858 self->priv->x_user_action_time = x_timestamp;
2861 empathy_chat_window_switch_to_chat (self, chat);
2863 /* Don't use tpaw_window_present_with_time () which would move the window
2864 * to our current desktop but move to the window's desktop instead. This is
2865 * more coherent with Shell's 'app is ready' notication which moves the view
2866 * to the app desktop rather than moving the app itself. */
2867 empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2869 gtk_widget_grab_focus (chat->input_text_view);
2870 return self;
2873 static void
2874 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2875 guint *nb_rooms,
2876 guint *nb_private)
2878 GList *l;
2879 guint _nb_rooms = 0, _nb_private = 0;
2881 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2883 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2884 _nb_rooms++;
2885 else
2886 _nb_private++;
2889 if (nb_rooms != NULL)
2890 *nb_rooms = _nb_rooms;
2891 if (nb_private != NULL)
2892 *nb_private = _nb_private;
2895 EmpathyIndividualManager *
2896 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2898 return self->priv->individual_mgr;