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>
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)) \
61 PROP_INDIVIDUAL_MGR
= 1
64 struct _EmpathyChatWindowPriv
66 EmpathyChat
*current_chat
;
69 gboolean dnd_same_window
;
70 EmpathyChatroomManager
*chatroom_manager
;
71 EmpathyNotifyManager
*notify_mgr
;
72 EmpathyIndividualManager
*individual_mgr
;
74 NotifyNotification
*notification
;
76 GtkTargetList
*contact_targets
;
77 GtkTargetList
*file_targets
;
79 EmpathyChatManager
*chat_manager
;
80 gulong chat_manager_chats_changed_id
;
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
125 DND_DRAG_TYPE_CONTACT_ID
,
126 DND_DRAG_TYPE_INDIVIDUAL_ID
,
127 DND_DRAG_TYPE_URI_LIST
,
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
,
162 static void empathy_chat_window_remove_chat (EmpathyChatWindow
*window
,
165 static void empathy_chat_window_move_chat (EmpathyChatWindow
*old_window
,
166 EmpathyChatWindow
*new_window
,
169 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow
*self
,
173 G_DEFINE_TYPE (EmpathyChatWindow
, empathy_chat_window
, GTK_TYPE_WINDOW
)
176 chat_window_accel_cb (GtkAccelGroup
*accelgroup
,
180 EmpathyChatWindow
*self
)
185 for (i
= 0; i
< G_N_ELEMENTS (tab_accel_keys
); i
++)
187 if (tab_accel_keys
[i
] == key
)
195 gtk_notebook_set_current_page (GTK_NOTEBOOK (self
->priv
->notebook
), num
);
198 static EmpathyChatWindow
*
199 chat_window_find_chat (EmpathyChat
*chat
)
203 for (l
= chat_windows
; l
; l
= l
->next
)
205 EmpathyChatWindow
*window
= l
->data
;
207 ll
= g_list_find (window
->priv
->chats
, chat
);
216 remove_all_chats (EmpathyChatWindow
*self
)
220 while (self
->priv
->chats
)
221 empathy_chat_window_remove_chat (self
, self
->priv
->chats
->data
);
223 g_object_unref (self
);
227 confirm_close_response_cb (GtkWidget
*dialog
,
229 EmpathyChatWindow
*window
)
233 chat
= g_object_get_data (G_OBJECT (dialog
), "chat");
235 gtk_widget_destroy (dialog
);
237 if (response
!= GTK_RESPONSE_ACCEPT
)
241 empathy_chat_window_remove_chat (window
, chat
);
243 remove_all_chats (window
);
247 confirm_close (EmpathyChatWindow
*self
,
248 gboolean close_window
,
253 gchar
*primary
, *secondary
;
255 g_return_if_fail (n_rooms
> 0);
258 g_return_if_fail (chat
== NULL
);
260 g_return_if_fail (chat
!= NULL
);
262 /* If there are no chats in this window, how could we possibly have got
265 g_return_if_fail (self
->priv
->chats
!= NULL
);
267 /* Treat closing a window which only has one tab exactly like closing
270 if (close_window
&& self
->priv
->chats
->next
== NULL
)
272 close_window
= FALSE
;
273 chat
= self
->priv
->chats
->data
;
278 primary
= g_strdup (_("Close this window?"));
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 "
292 secondary
= g_strdup_printf (
293 /* Note to translators: the number of chats will
294 * always be at least 2.
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.",
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."));
315 dialog
= gtk_message_dialog_new (
317 GTK_DIALOG_MODAL
| GTK_DIALOG_DESTROY_WITH_PARENT
,
322 gtk_window_set_title (GTK_WINDOW (dialog
), "");
323 g_object_set (dialog
, "secondary-text", secondary
, NULL
);
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
);
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.
349 chat_needs_close_confirmation (EmpathyChat
*chat
)
351 return (empathy_chat_is_room (chat
) &&
352 empathy_chat_get_tp_chat (chat
) != NULL
);
356 maybe_close_chat (EmpathyChatWindow
*window
,
359 g_return_if_fail (chat
!= NULL
);
361 if (chat_needs_close_confirmation (chat
))
362 confirm_close (window
, FALSE
, 1, chat
);
364 empathy_chat_window_remove_chat (window
, chat
);
368 chat_window_close_clicked_cb (GtkAction
*action
,
371 EmpathyChatWindow
*window
;
373 window
= chat_window_find_chat (chat
);
374 maybe_close_chat (window
, chat
);
378 chat_tab_style_updated_cb (GtkWidget
*hbox
,
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
,
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
);
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",
435 gtk_widget_show (image
);
437 gtk_container_add (GTK_CONTAINER (button
), image
);
443 chat_window_create_label (EmpathyChatWindow
*window
,
445 gboolean is_tab_label
)
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
);
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",
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",
490 g_object_set_data (G_OBJECT (chat
),
491 is_tab_label
? "chat-window-tab-tooltip-widget" :
492 "chat-window-menu-tooltip-widget",
495 gtk_container_add (GTK_CONTAINER (event_box
), event_box_hbox
);
496 gtk_box_pack_start (GTK_BOX (hbox
), event_box
, TRUE
, TRUE
, 0);
500 GtkWidget
*close_button
;
501 GtkWidget
*sending_spinner
;
503 sending_spinner
= gtk_spinner_new ();
505 gtk_box_pack_start (GTK_BOX (hbox
), sending_spinner
,
507 g_object_set_data (G_OBJECT (chat
),
508 "chat-window-tab-sending-spinner",
511 close_button
= create_close_button ();
512 g_object_set_data (G_OBJECT (chat
), "chat-window-tab-close-button",
515 gtk_box_pack_end (GTK_BOX (hbox
), close_button
, FALSE
, FALSE
, 0);
517 g_signal_connect (close_button
,
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
);
532 _submenu_notify_visible_changed_cb (GObject
*object
,
536 g_signal_handlers_disconnect_by_func (object
,
537 _submenu_notify_visible_changed_cb
, userdata
);
539 chat_window_update (EMPATHY_CHAT_WINDOW (userdata
), TRUE
);
543 chat_window_menu_context_update (EmpathyChatWindow
*self
,
548 gboolean wrap_around
;
549 gboolean is_connected
;
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",
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
||
562 gtk_action_set_sensitive (self
->priv
->menu_tabs_prev
, (!first_page
||
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
);
571 chat_window_conversation_menu_update (EmpathyChatWindow
*self
)
573 EmpathyTpChat
*tp_chat
;
574 TpConnection
*connection
;
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
);
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
);
597 chat_window_contact_menu_update (EmpathyChatWindow
*self
)
599 GtkWidget
*menu
, *submenu
, *orig_submenu
;
601 if (self
->priv
->updating_menu
)
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
);
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
);
624 gtk_widget_set_sensitive (menu
, FALSE
);
629 tp_g_signal_connect_object (orig_submenu
,
631 (GCallback
)_submenu_notify_visible_changed_cb
, self
, 0);
634 self
->priv
->updating_menu
= FALSE
;
638 get_all_unread_messages (EmpathyChatWindow
*self
)
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
));
650 get_window_title_name (EmpathyChatWindow
*self
)
652 gchar
*active_name
, *ret
;
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
);
667 if (current_unread_msgs
== 0)
668 ret
= g_strdup (active_name
);
670 ret
= g_strdup_printf (ngettext (
672 "%s (%d unread)", current_unread_msgs
),
673 active_name
, current_unread_msgs
);
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 (
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 (
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)",
705 active_name
, all_unread_msgs
);
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)",
714 active_name
, all_unread_msgs
);
718 g_free (active_name
);
724 chat_window_title_update (EmpathyChatWindow
*self
)
728 name
= get_window_title_name (self
);
729 gtk_window_set_title (GTK_WINDOW (self
), name
);
734 chat_window_icon_update (EmpathyChatWindow
*self
,
735 gboolean new_messages
)
738 EmpathyContact
*remote_contact
;
739 gboolean avatar_in_icon
;
742 n_chats
= g_list_length (self
->priv
->chats
);
744 /* Update window icon */
747 gtk_window_set_icon_name (GTK_WINDOW (self
),
748 EMPATHY_IMAGE_MESSAGE
);
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
,
760 gtk_window_set_icon (GTK_WINDOW (self
), icon
);
763 g_object_unref (icon
);
767 gtk_window_set_icon_name (GTK_WINDOW (self
), NULL
);
773 chat_window_close_button_update (EmpathyChatWindow
*self
,
777 GtkWidget
*chat_close_button
;
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
);
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
);
800 chat_window_update (EmpathyChatWindow
*self
,
801 gboolean update_contact_menu
)
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
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
);
829 append_markup_printf (GString
*string
,
836 va_start (args
, format
);
838 tmp
= g_markup_vprintf_escaped (format
, args
);
839 g_string_append (string
, tmp
);
846 chat_window_update_chat_tab_full (EmpathyChat
*chat
,
847 gboolean update_contact_menu
)
849 EmpathyChatWindow
*self
;
850 EmpathyContact
*remote_contact
;
854 const gchar
*subject
;
855 const gchar
*status
= NULL
;
859 const gchar
*icon_name
;
860 GtkWidget
*tab_image
;
861 GtkWidget
*menu_image
;
862 GtkWidget
*sending_spinner
;
865 self
= chat_window_find_chat (chat
);
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, "
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 */
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
);
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
,
913 gtk_widget_show (tab_image
);
914 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image
), icon_name
,
916 gtk_widget_show (menu_image
);
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,
934 /* Update tab tooltip */
935 tooltip
= g_string_new (NULL
);
939 id
= empathy_contact_get_id (remote_contact
);
940 status
= empathy_contact_get_presence_message (remote_contact
);
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
));
955 char *tmp
= g_strdup_printf (
956 ngettext ("Sending %d message",
957 "Sending %d messages",
961 g_string_append (tooltip
, "\n");
962 g_string_append (tooltip
, tmp
);
964 gtk_widget_set_tooltip_text (sending_spinner
, 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 ! */
988 name
= g_strdup_printf ("☎ %s", name
);
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
);
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>",
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
);
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
);
1029 chat_window_update_chat_tab (EmpathyChat
*chat
)
1031 chat_window_update_chat_tab_full (chat
, TRUE
);
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. */
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
);
1066 chat_window_update (window
, FALSE
);
1070 chat_window_insert_smiley_activate_cb (EmpathySmileyManager
*manager
,
1071 EmpathySmiley
*smiley
,
1074 EmpathyChatWindow
*self
= user_data
;
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
);
1085 chat_window_conv_activate_cb (GtkAction
*action
,
1086 EmpathyChatWindow
*self
)
1090 EmpathyContact
*remote_contact
= NULL
;
1091 gboolean disconnected
;
1093 /* Favorite room menu */
1094 is_room
= empathy_chat_is_room (self
->priv
->current_chat
);
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
,
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
,
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
);
1143 gtk_action_set_visible (self
->priv
->menu_conv_join_chat
, TRUE
);
1144 gtk_action_set_visible (self
->priv
->menu_conv_leave_chat
, FALSE
);
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
);
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
,
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
);
1180 chat_window_clear_activate_cb (GtkAction
*action
,
1181 EmpathyChatWindow
*self
)
1183 empathy_chat_clear (self
->priv
->current_chat
);
1187 chat_window_favorite_toggled_cb (GtkToggleAction
*toggle_action
,
1188 EmpathyChatWindow
*self
)
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
);
1210 chat_window_always_urgent_toggled_cb (GtkToggleAction
*toggle_action
,
1211 EmpathyChatWindow
*self
)
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
);
1233 chat_window_contacts_toggled_cb (GtkToggleAction
*toggle_action
,
1234 EmpathyChatWindow
*self
)
1238 active
= gtk_toggle_action_get_active (toggle_action
);
1240 empathy_chat_set_show_contacts (self
->priv
->current_chat
, active
);
1244 chat_window_invite_participant_activate_cb (GtkAction
*action
,
1245 EmpathyChatWindow
*self
)
1248 EmpathyTpChat
*tp_chat
;
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
)
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
);
1280 gtk_widget_destroy (dialog
);
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
));
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
, "");
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
,
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);
1413 gtk_notebook_next_page (GTK_NOTEBOOK (self
->priv
->notebook
));
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
,
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
),
1437 gtk_notebook_prev_page (GTK_NOTEBOOK (self
->priv
->notebook
));
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 ());
1449 chat_window_tabs_left_activate_cb (GtkAction
*action
,
1450 EmpathyChatWindow
*self
)
1453 gint index_
, num_pages
;
1455 chat
= self
->priv
->current_chat
;
1456 index_
= gtk_notebook_get_current_page (GTK_NOTEBOOK (self
->priv
->notebook
));
1460 gtk_notebook_reorder_child (GTK_NOTEBOOK (self
->priv
->notebook
), GTK_WIDGET (chat
),
1463 num_pages
= gtk_notebook_get_n_pages (GTK_NOTEBOOK (self
->priv
->notebook
));
1464 chat_window_menu_context_update (self
, num_pages
);
1468 chat_window_tabs_right_activate_cb (GtkAction
*action
,
1469 EmpathyChatWindow
*self
)
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
),
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,
1496 chat_window_detach_activate_cb (GtkAction
*action
,
1497 EmpathyChatWindow
*self
)
1499 EmpathyChatWindow
*new_window
;
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
));
1511 chat_window_help_contents_activate_cb (GtkAction
*action
,
1512 EmpathyChatWindow
*self
)
1514 empathy_url_show (GTK_WIDGET (self
), "help:empathy");
1518 chat_window_help_about_activate_cb (GtkAction
*action
,
1519 EmpathyChatWindow
*self
)
1521 empathy_about_dialog_new (GTK_WINDOW (self
));
1525 chat_window_delete_event_cb (GtkWidget
*dialog
,
1527 EmpathyChatWindow
*self
)
1529 EmpathyChat
*chat
= NULL
;
1533 DEBUG ("Delete event received");
1535 for (l
= self
->priv
->chats
; l
!= NULL
; l
= l
->next
)
1537 if (chat_needs_close_confirmation (l
->data
))
1546 confirm_close (self
, TRUE
, n_rooms
, (n_rooms
== 1 ? chat
: NULL
));
1550 remove_all_chats (self
);
1557 chat_window_composing_cb (EmpathyChat
*chat
,
1558 gboolean is_composing
,
1559 EmpathyChatWindow
*self
)
1561 chat_window_update_chat_tab (chat
);
1565 chat_window_set_urgency_hint (EmpathyChatWindow
*self
,
1568 gtk_window_set_urgency_hint (GTK_WINDOW (self
), urgent
);
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
;
1581 chat_window_show_or_update_notification (EmpathyChatWindow
*self
,
1582 EmpathyMessage
*message
,
1585 EmpathyContact
*sender
;
1586 const gchar
*header
;
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
))
1596 res
= g_settings_get_boolean (self
->priv
->gsettings_notif
,
1597 EMPATHY_PREFS_NOTIFICATIONS_FOCUS
);
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
);
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
,
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
);
1655 notify_notification_set_icon_from_pixbuf (notification
, pixbuf
);
1656 g_object_unref (pixbuf
);
1659 notify_notification_show (notification
, NULL
);
1665 empathy_chat_window_has_focus (EmpathyChatWindow
*self
)
1669 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self
), FALSE
);
1671 g_object_get (self
, "has-toplevel-focus", &has_focus
, NULL
);
1677 chat_window_new_message_cb (EmpathyChat
*chat
,
1678 EmpathyMessage
*message
,
1680 gboolean should_highlight
,
1681 EmpathyChatWindow
*self
)
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
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
);
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
);
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
))
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
,
1739 if (chatroom
!= NULL
&& empathy_chatroom_is_always_urgent (chatroom
))
1740 needs_urgency
= TRUE
;
1742 needs_urgency
= should_highlight
;
1746 needs_urgency
= TRUE
;
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
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
);
1772 chat_window_command_part (EmpathyChat
*chat
,
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
);
1784 empathy_tp_chat_leave (tp_chat
, "");
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
);
1798 empathy_tp_chat_leave (tp_chat
, strv
[2]);
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
);
1816 empathy_tp_chat_leave (tp_chat
, message
);
1822 static GtkNotebook
*
1823 notebook_create_window_cb (GtkNotebook
*source
,
1829 EmpathyChatWindow
*window
, *new_window
;
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
);
1848 chat_window_page_switched_cb (GtkNotebook
*notebook
,
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
)
1867 self
->priv
->current_chat
= chat
;
1868 empathy_chat_messages_read (chat
);
1870 chat_window_update_chat_tab (chat
);
1874 chat_window_page_added_cb (GtkNotebook
*notebook
,
1877 EmpathyChatWindow
*self
)
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
;
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
);
1919 chat_window_page_removed_cb (GtkNotebook
*notebook
,
1922 EmpathyChatWindow
*self
)
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)");
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
));
1959 chat_window_update (self
, TRUE
);
1964 chat_window_focus_in_event_cb (GtkWidget
*widget
,
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
);
1979 contacts_loaded_cb (EmpathyIndividualManager
*mgr
,
1980 EmpathyChatWindow
*self
)
1982 chat_window_contact_menu_update (self
);
1986 chat_window_focus_out_event_cb (GtkWidget
*widget
,
1988 EmpathyChatWindow
*self
)
1990 if (self
->priv
->individual_mgr
!= NULL
)
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");
2025 chat_window_drag_drop (GtkWidget
*widget
,
2026 GdkDragContext
*context
,
2030 EmpathyChatWindow
*self
)
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_
);
2048 chat_window_drag_motion (GtkWidget
*widget
,
2049 GdkDragContext
*context
,
2053 EmpathyChatWindow
*self
)
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
2076 if ((contact
== NULL
) || !empathy_contact_is_online (contact
))
2078 gdk_drag_status (context
, 0, time_
);
2082 if (!(empathy_contact_get_capabilities (contact
)
2083 & EMPATHY_CAPABILITIES_FT
))
2085 gdk_drag_status (context
, 0, time_
);
2089 gdk_drag_status (context
, GDK_ACTION_COPY
, time_
);
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_
);
2109 drag_data_received_individual_id (EmpathyChatWindow
*self
,
2111 GdkDragContext
*context
,
2114 GtkSelectionData
*selection
,
2119 FolksIndividual
*individual
;
2120 EmpathyTpChat
*chat
;
2121 TpContact
*tp_contact
;
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
)
2132 chat
= empathy_chat_get_tp_chat (self
->priv
->current_chat
);
2136 if (!empathy_tp_chat_can_add_contact (chat
))
2138 DEBUG ("Can't invite contact to %s",
2139 tp_proxy_get_object_path (chat
));
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. */
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
);
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
);
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
);
2173 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2177 chat_window_drag_data_received (GtkWidget
*widget
,
2178 GdkDragContext
*context
,
2181 GtkSelectionData
*selection
,
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
;
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
)
2220 gtk_drag_finish (context
, FALSE
, FALSE
, time_
);
2226 empathy_chat_with_contact_id (account
, contact_id
,
2227 empathy_get_current_action_time (), NULL
, NULL
);
2235 old_window
= chat_window_find_chat (chat
);
2238 if (old_window
== self
)
2240 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2244 empathy_chat_window_move_chat (old_window
, self
, chat
);
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
;
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_
);
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
)
2291 EmpathyChatWindow
*old_window
= NULL
;
2295 chat
= (void *) gtk_selection_data_get_data (selection
);
2296 old_window
= chat_window_find_chat (*chat
);
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");
2308 DEBUG ("DND from unknown source");
2309 gtk_drag_finish (context
, FALSE
, FALSE
, time_
);
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);
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
);
2364 chat_window_get_property (GObject
*object
,
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
);
2376 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
2382 empathy_chat_window_class_init (EmpathyChatWindowClass
*klass
)
2384 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
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
));
2400 empathy_chat_window_init (EmpathyChatWindow
*self
)
2403 GtkAccelGroup
*accel_group
;
2408 GtkWidget
*chat_vbox
;
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
,
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
,
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
);
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
,
2495 gtk_accel_group_connect (accel_group
, tab_accel_keys
[i
], GDK_MOD1_MASK
, 0,
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
,
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
);
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 */
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)
2603 /* Skip the window if there aren't any 1-1 chats in it */
2604 if (!room
&& nb_private
== 0)
2614 empathy_chat_window_add_chat (EmpathyChatWindow
*self
,
2618 GtkWidget
*popup_label
;
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
)
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
,
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
);
2700 empathy_chat_window_remove_chat (EmpathyChatWindow
*self
,
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");
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
),
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
);
2736 empathy_chat_window_move_chat (EmpathyChatWindow
*old_window
,
2737 EmpathyChatWindow
*new_window
,
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
);
2765 empathy_chat_window_switch_to_chat (EmpathyChatWindow
*self
,
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
),
2776 gtk_notebook_set_current_page (GTK_NOTEBOOK (self
->priv
->notebook
),
2781 empathy_chat_window_find_chat (TpAccount
*account
,
2783 gboolean sms_channel
)
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
;
2794 for (ll
= window
->priv
->chats
; ll
; ll
= ll
->next
)
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
))
2811 empathy_chat_window_present_chat (EmpathyChat
*chat
,
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 */
2824 self
= empathy_chat_window_get_default (empathy_chat_is_room (chat
));
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
))
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
2849 if (self
->priv
->x_user_action_time
!= 0
2850 && X_EARLIER_OR_EQL (x_timestamp
, self
->priv
->x_user_action_time
))
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
);
2869 empathy_chat_window_get_nb_chats (EmpathyChatWindow
*self
,
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
)))
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
;