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 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
,
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
);
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",
440 gtk_widget_show (image
);
442 gtk_container_add (GTK_CONTAINER (button
), image
);
448 chat_window_create_label (EmpathyChatWindow
*window
,
450 gboolean is_tab_label
)
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
);
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",
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",
495 g_object_set_data (G_OBJECT (chat
),
496 is_tab_label
? "chat-window-tab-tooltip-widget" :
497 "chat-window-menu-tooltip-widget",
500 gtk_container_add (GTK_CONTAINER (event_box
), event_box_hbox
);
501 gtk_box_pack_start (GTK_BOX (hbox
), event_box
, TRUE
, TRUE
, 0);
505 GtkWidget
*close_button
;
506 GtkWidget
*sending_spinner
;
508 sending_spinner
= gtk_spinner_new ();
510 gtk_box_pack_start (GTK_BOX (hbox
), sending_spinner
,
512 g_object_set_data (G_OBJECT (chat
),
513 "chat-window-tab-sending-spinner",
516 close_button
= create_close_button ();
517 g_object_set_data (G_OBJECT (chat
), "chat-window-tab-close-button",
520 gtk_box_pack_end (GTK_BOX (hbox
), close_button
, FALSE
, FALSE
, 0);
522 g_signal_connect (close_button
,
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
);
537 _submenu_notify_visible_changed_cb (GObject
*object
,
541 g_signal_handlers_disconnect_by_func (object
,
542 _submenu_notify_visible_changed_cb
, userdata
);
544 chat_window_update (EMPATHY_CHAT_WINDOW (userdata
), TRUE
);
548 chat_window_menu_context_update (EmpathyChatWindow
*self
,
553 gboolean wrap_around
;
554 gboolean is_connected
;
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",
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
||
567 gtk_action_set_sensitive (self
->priv
->menu_tabs_prev
, (!first_page
||
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
);
576 chat_window_conversation_menu_update (EmpathyChatWindow
*self
)
578 EmpathyTpChat
*tp_chat
;
579 TpConnection
*connection
;
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
);
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
);
602 chat_window_contact_menu_update (EmpathyChatWindow
*self
)
604 GtkWidget
*menu
, *submenu
, *orig_submenu
;
606 if (self
->priv
->updating_menu
)
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
);
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
);
629 gtk_widget_set_sensitive (menu
, FALSE
);
634 tp_g_signal_connect_object (orig_submenu
,
636 (GCallback
)_submenu_notify_visible_changed_cb
, self
, 0);
639 self
->priv
->updating_menu
= FALSE
;
643 get_all_unread_messages (EmpathyChatWindow
*self
)
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
));
655 get_window_title_name (EmpathyChatWindow
*self
)
657 gchar
*active_name
, *ret
;
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
);
672 if (current_unread_msgs
== 0)
673 ret
= g_strdup (active_name
);
675 ret
= g_strdup_printf (ngettext (
677 "%s (%d unread)", current_unread_msgs
),
678 active_name
, current_unread_msgs
);
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 (
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 (
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)",
710 active_name
, all_unread_msgs
);
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)",
719 active_name
, all_unread_msgs
);
723 g_free (active_name
);
729 chat_window_title_update (EmpathyChatWindow
*self
)
733 name
= get_window_title_name (self
);
734 gtk_window_set_title (GTK_WINDOW (self
), name
);
739 chat_window_icon_update (EmpathyChatWindow
*self
,
740 gboolean new_messages
)
743 EmpathyContact
*remote_contact
;
744 gboolean avatar_in_icon
;
747 n_chats
= g_list_length (self
->priv
->chats
);
749 /* Update window icon */
752 gtk_window_set_icon_name (GTK_WINDOW (self
),
753 EMPATHY_IMAGE_MESSAGE
);
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
,
765 gtk_window_set_icon (GTK_WINDOW (self
), icon
);
768 g_object_unref (icon
);
772 gtk_window_set_icon_name (GTK_WINDOW (self
), NULL
);
778 chat_window_close_button_update (EmpathyChatWindow
*self
,
782 GtkWidget
*chat_close_button
;
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
);
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
);
805 chat_window_update (EmpathyChatWindow
*self
,
806 gboolean update_contact_menu
)
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
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
);
834 append_markup_printf (GString
*string
,
841 va_start (args
, format
);
843 tmp
= g_markup_vprintf_escaped (format
, args
);
844 g_string_append (string
, tmp
);
851 chat_window_update_chat_tab_full (EmpathyChat
*chat
,
852 gboolean update_contact_menu
)
854 EmpathyChatWindow
*self
;
855 EmpathyContact
*remote_contact
;
859 const gchar
*subject
;
860 const gchar
*status
= NULL
;
864 const gchar
*icon_name
;
865 GtkWidget
*tab_image
;
866 GtkWidget
*menu_image
;
867 GtkWidget
*sending_spinner
;
870 self
= chat_window_find_chat (chat
);
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, "
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 */
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
);
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
,
918 gtk_widget_show (tab_image
);
919 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image
), icon_name
,
921 gtk_widget_show (menu_image
);
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,
939 /* Update tab tooltip */
940 tooltip
= g_string_new (NULL
);
944 id
= empathy_contact_get_id (remote_contact
);
945 status
= empathy_contact_get_presence_message (remote_contact
);
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
));
960 char *tmp
= g_strdup_printf (
961 ngettext ("Sending %d message",
962 "Sending %d messages",
966 g_string_append (tooltip
, "\n");
967 g_string_append (tooltip
, tmp
);
969 gtk_widget_set_tooltip_text (sending_spinner
, 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 ! */
993 name
= g_strdup_printf ("☎ %s", name
);
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
);
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>",
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
);
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
);
1034 chat_window_update_chat_tab (EmpathyChat
*chat
)
1036 chat_window_update_chat_tab_full (chat
, TRUE
);
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. */
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
);
1071 chat_window_update (window
, FALSE
);
1075 chat_window_insert_smiley_activate_cb (EmpathySmileyManager
*manager
,
1076 EmpathySmiley
*smiley
,
1079 EmpathyChatWindow
*self
= user_data
;
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
);
1090 chat_window_conv_activate_cb (GtkAction
*action
,
1091 EmpathyChatWindow
*self
)
1095 EmpathyContact
*remote_contact
= NULL
;
1096 gboolean disconnected
;
1098 /* Favorite room menu */
1099 is_room
= empathy_chat_is_room (self
->priv
->current_chat
);
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
,
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
,
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
);
1148 gtk_action_set_visible (self
->priv
->menu_conv_join_chat
, TRUE
);
1149 gtk_action_set_visible (self
->priv
->menu_conv_leave_chat
, FALSE
);
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
);
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
,
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
);
1185 chat_window_clear_activate_cb (GtkAction
*action
,
1186 EmpathyChatWindow
*self
)
1188 empathy_chat_clear (self
->priv
->current_chat
);
1192 chat_window_favorite_toggled_cb (GtkToggleAction
*toggle_action
,
1193 EmpathyChatWindow
*self
)
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
);
1215 chat_window_always_urgent_toggled_cb (GtkToggleAction
*toggle_action
,
1216 EmpathyChatWindow
*self
)
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
);
1238 chat_window_contacts_toggled_cb (GtkToggleAction
*toggle_action
,
1239 EmpathyChatWindow
*self
)
1243 active
= gtk_toggle_action_get_active (toggle_action
);
1245 empathy_chat_set_show_contacts (self
->priv
->current_chat
, active
);
1249 chat_window_invite_participant_activate_cb (GtkAction
*action
,
1250 EmpathyChatWindow
*self
)
1253 EmpathyTpChat
*tp_chat
;
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
)
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
);
1285 gtk_widget_destroy (dialog
);
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
));
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
, "");
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
,
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);
1418 gtk_notebook_next_page (GTK_NOTEBOOK (self
->priv
->notebook
));
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
,
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
),
1442 gtk_notebook_prev_page (GTK_NOTEBOOK (self
->priv
->notebook
));
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 ());
1454 chat_window_tabs_left_activate_cb (GtkAction
*action
,
1455 EmpathyChatWindow
*self
)
1458 gint index_
, num_pages
;
1460 chat
= self
->priv
->current_chat
;
1461 index_
= gtk_notebook_get_current_page (GTK_NOTEBOOK (self
->priv
->notebook
));
1465 gtk_notebook_reorder_child (GTK_NOTEBOOK (self
->priv
->notebook
), GTK_WIDGET (chat
),
1468 num_pages
= gtk_notebook_get_n_pages (GTK_NOTEBOOK (self
->priv
->notebook
));
1469 chat_window_menu_context_update (self
, num_pages
);
1473 chat_window_tabs_right_activate_cb (GtkAction
*action
,
1474 EmpathyChatWindow
*self
)
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
),
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,
1501 chat_window_detach_activate_cb (GtkAction
*action
,
1502 EmpathyChatWindow
*self
)
1504 EmpathyChatWindow
*new_window
;
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
));
1516 chat_window_help_contents_activate_cb (GtkAction
*action
,
1517 EmpathyChatWindow
*self
)
1519 empathy_url_show (GTK_WIDGET (self
), "help:empathy");
1523 chat_window_help_about_activate_cb (GtkAction
*action
,
1524 EmpathyChatWindow
*self
)
1526 empathy_about_dialog_new (GTK_WINDOW (self
));
1530 chat_window_delete_event_cb (GtkWidget
*dialog
,
1532 EmpathyChatWindow
*self
)
1534 EmpathyChat
*chat
= NULL
;
1538 DEBUG ("Delete event received");
1540 for (l
= self
->priv
->chats
; l
!= NULL
; l
= l
->next
)
1542 if (chat_needs_close_confirmation (l
->data
))
1551 confirm_close (self
, TRUE
, n_rooms
, (n_rooms
== 1 ? chat
: NULL
));
1555 remove_all_chats (self
);
1562 chat_window_composing_cb (EmpathyChat
*chat
,
1563 gboolean is_composing
,
1564 EmpathyChatWindow
*self
)
1566 chat_window_update_chat_tab (chat
);
1570 chat_window_set_urgency_hint (EmpathyChatWindow
*self
,
1573 gtk_window_set_urgency_hint (GTK_WINDOW (self
), urgent
);
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
;
1586 chat_window_show_or_update_notification (EmpathyChatWindow
*self
,
1587 EmpathyMessage
*message
,
1590 EmpathyContact
*sender
;
1591 const gchar
*header
;
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
))
1601 res
= g_settings_get_boolean (self
->priv
->gsettings_notif
,
1602 EMPATHY_PREFS_NOTIFICATIONS_FOCUS
);
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
);
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
,
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
);
1660 notify_notification_set_icon_from_pixbuf (notification
, pixbuf
);
1661 g_object_unref (pixbuf
);
1664 notify_notification_show (notification
, NULL
);
1670 empathy_chat_window_has_focus (EmpathyChatWindow
*self
)
1674 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self
), FALSE
);
1676 g_object_get (self
, "has-toplevel-focus", &has_focus
, NULL
);
1682 chat_window_new_message_cb (EmpathyChat
*chat
,
1683 EmpathyMessage
*message
,
1685 gboolean should_highlight
,
1686 EmpathyChatWindow
*self
)
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
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
);
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
);
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
))
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
,
1744 if (chatroom
!= NULL
&& empathy_chatroom_is_always_urgent (chatroom
))
1745 needs_urgency
= TRUE
;
1747 needs_urgency
= should_highlight
;
1751 needs_urgency
= TRUE
;
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
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
);
1777 chat_window_command_part (EmpathyChat
*chat
,
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
);
1789 empathy_tp_chat_leave (tp_chat
, "");
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
);
1803 empathy_tp_chat_leave (tp_chat
, strv
[2]);
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
);
1821 empathy_tp_chat_leave (tp_chat
, message
);
1827 static GtkNotebook
*
1828 notebook_create_window_cb (GtkNotebook
*source
,
1834 EmpathyChatWindow
*window
, *new_window
;
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
);
1853 chat_window_page_switched_cb (GtkNotebook
*notebook
,
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
)
1872 self
->priv
->current_chat
= chat
;
1873 empathy_chat_messages_read (chat
);
1875 chat_window_update_chat_tab (chat
);
1879 chat_window_page_added_cb (GtkNotebook
*notebook
,
1882 EmpathyChatWindow
*self
)
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
;
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
);
1924 chat_window_page_removed_cb (GtkNotebook
*notebook
,
1927 EmpathyChatWindow
*self
)
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)");
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
));
1964 chat_window_update (self
, TRUE
);
1969 chat_window_focus_in_event_cb (GtkWidget
*widget
,
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
);
1984 contacts_loaded_cb (EmpathyIndividualManager
*mgr
,
1985 EmpathyChatWindow
*self
)
1987 chat_window_contact_menu_update (self
);
1991 chat_window_focus_out_event_cb (GtkWidget
*widget
,
1993 EmpathyChatWindow
*self
)
1995 if (self
->priv
->individual_mgr
!= NULL
)
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");
2030 chat_window_drag_drop (GtkWidget
*widget
,
2031 GdkDragContext
*context
,
2035 EmpathyChatWindow
*self
)
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_
);
2053 chat_window_drag_motion (GtkWidget
*widget
,
2054 GdkDragContext
*context
,
2058 EmpathyChatWindow
*self
)
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
2081 if ((contact
== NULL
) || !empathy_contact_is_online (contact
))
2083 gdk_drag_status (context
, 0, time_
);
2087 if (!(empathy_contact_get_capabilities (contact
)
2088 & EMPATHY_CAPABILITIES_FT
))
2090 gdk_drag_status (context
, 0, time_
);
2094 gdk_drag_status (context
, GDK_ACTION_COPY
, time_
);
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_
);
2114 drag_data_received_individual_id (EmpathyChatWindow
*self
,
2116 GdkDragContext
*context
,
2119 GtkSelectionData
*selection
,
2124 FolksIndividual
*individual
;
2125 EmpathyTpChat
*chat
;
2126 TpContact
*tp_contact
;
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
)
2137 chat
= empathy_chat_get_tp_chat (self
->priv
->current_chat
);
2141 if (!empathy_tp_chat_can_add_contact (chat
))
2143 DEBUG ("Can't invite contact to %s",
2144 tp_proxy_get_object_path (chat
));
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. */
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
);
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
);
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
);
2178 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2182 chat_window_drag_data_received (GtkWidget
*widget
,
2183 GdkDragContext
*context
,
2186 GtkSelectionData
*selection
,
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
;
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
)
2225 gtk_drag_finish (context
, FALSE
, FALSE
, time_
);
2231 empathy_chat_with_contact_id (account
, contact_id
,
2232 empathy_get_current_action_time (), NULL
, NULL
);
2240 old_window
= chat_window_find_chat (chat
);
2243 if (old_window
== self
)
2245 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2249 empathy_chat_window_move_chat (old_window
, self
, chat
);
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
;
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_
);
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
)
2296 EmpathyChatWindow
*old_window
= NULL
;
2300 chat
= (void *) gtk_selection_data_get_data (selection
);
2301 old_window
= chat_window_find_chat (*chat
);
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");
2313 DEBUG ("DND from unknown source");
2314 gtk_drag_finish (context
, FALSE
, FALSE
, time_
);
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);
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
);
2369 chat_window_get_property (GObject
*object
,
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
);
2381 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
2387 empathy_chat_window_class_init (EmpathyChatWindowClass
*klass
)
2389 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
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
));
2405 empathy_chat_window_init (EmpathyChatWindow
*self
)
2408 GtkAccelGroup
*accel_group
;
2413 GtkWidget
*chat_vbox
;
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
,
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
,
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
);
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
,
2500 gtk_accel_group_connect (accel_group
, tab_accel_keys
[i
], GDK_MOD1_MASK
, 0,
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
,
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
);
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 */
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)
2608 /* Skip the window if there aren't any 1-1 chats in it */
2609 if (!room
&& nb_private
== 0)
2619 empathy_chat_window_add_chat (EmpathyChatWindow
*self
,
2623 GtkWidget
*popup_label
;
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
)
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
,
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
);
2705 empathy_chat_window_remove_chat (EmpathyChatWindow
*self
,
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");
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
),
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
);
2741 empathy_chat_window_move_chat (EmpathyChatWindow
*old_window
,
2742 EmpathyChatWindow
*new_window
,
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
);
2770 empathy_chat_window_switch_to_chat (EmpathyChatWindow
*self
,
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
),
2781 gtk_notebook_set_current_page (GTK_NOTEBOOK (self
->priv
->notebook
),
2786 empathy_chat_window_find_chat (TpAccount
*account
,
2788 gboolean sms_channel
)
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
;
2799 for (ll
= window
->priv
->chats
; ll
; ll
= ll
->next
)
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
))
2816 empathy_chat_window_present_chat (EmpathyChat
*chat
,
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 */
2829 self
= empathy_chat_window_get_default (empathy_chat_is_room (chat
));
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
))
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
2854 if (self
->priv
->x_user_action_time
!= 0
2855 && X_EARLIER_OR_EQL (x_timestamp
, self
->priv
->x_user_action_time
))
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
);
2874 empathy_chat_window_get_nb_chats (EmpathyChatWindow
*self
,
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
)))
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
;