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"
48 #include "empathy-new-message-dialog.h"
50 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
51 #include "empathy-debug.h"
53 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
55 #define X_EARLIER_OR_EQL(t1, t2) \
56 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
57 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
62 PROP_INDIVIDUAL_MGR
= 1
65 struct _EmpathyChatWindowPriv
67 EmpathyChat
*current_chat
;
70 gboolean dnd_same_window
;
71 EmpathyChatroomManager
*chatroom_manager
;
72 EmpathyNotifyManager
*notify_mgr
;
73 EmpathyIndividualManager
*individual_mgr
;
75 NotifyNotification
*notification
;
77 GtkTargetList
*contact_targets
;
78 GtkTargetList
*file_targets
;
80 EmpathyChatManager
*chat_manager
;
81 gulong chat_manager_chats_changed_id
;
84 GtkUIManager
*ui_manager
;
85 GtkAction
*menu_conv_insert_smiley
;
86 GtkAction
*menu_conv_favorite
;
87 GtkAction
*menu_conv_join_chat
;
88 GtkAction
*menu_conv_leave_chat
;
89 GtkAction
*menu_conv_always_urgent
;
90 GtkAction
*menu_conv_toggle_contacts
;
92 GtkAction
*menu_edit_cut
;
93 GtkAction
*menu_edit_copy
;
94 GtkAction
*menu_edit_paste
;
95 GtkAction
*menu_edit_find
;
97 GtkAction
*menu_tabs_next
;
98 GtkAction
*menu_tabs_prev
;
99 GtkAction
*menu_tabs_undo_close_tab
;
100 GtkAction
*menu_tabs_left
;
101 GtkAction
*menu_tabs_right
;
102 GtkAction
*menu_tabs_detach
;
104 /* Last user action time we acted upon to show a tab */
105 guint32 x_user_action_time
;
107 GSettings
*gsettings_chat
;
108 GSettings
*gsettings_notif
;
109 GSettings
*gsettings_ui
;
111 EmpathySoundManager
*sound_mgr
;
113 gboolean updating_menu
;
116 static GList
*chat_windows
= NULL
;
118 static const guint tab_accel_keys
[] =
120 GDK_KEY_1
, GDK_KEY_2
, GDK_KEY_3
, GDK_KEY_4
, GDK_KEY_5
,
121 GDK_KEY_6
, GDK_KEY_7
, GDK_KEY_8
, GDK_KEY_9
, GDK_KEY_0
126 DND_DRAG_TYPE_CONTACT_ID
,
127 DND_DRAG_TYPE_INDIVIDUAL_ID
,
128 DND_DRAG_TYPE_URI_LIST
,
132 static const GtkTargetEntry drag_types_dest
[] =
134 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID
},
135 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID
},
136 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP
, DND_DRAG_TYPE_TAB
},
137 /* FIXME: disabled because of bug #640513
138 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
139 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact
[] =
145 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID
},
146 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID
},
149 static const GtkTargetEntry drag_types_dest_file
[] =
151 /* must be first to be prioritized, in order to receive the
152 * note's file path from Tomboy instead of an URI */
153 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST
},
154 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST
},
157 static void chat_window_update (EmpathyChatWindow
*window
,
158 gboolean update_contact_menu
);
160 static void empathy_chat_window_add_chat (EmpathyChatWindow
*window
,
163 static void empathy_chat_window_remove_chat (EmpathyChatWindow
*window
,
166 static void empathy_chat_window_move_chat (EmpathyChatWindow
*old_window
,
167 EmpathyChatWindow
*new_window
,
170 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow
*self
,
174 G_DEFINE_TYPE (EmpathyChatWindow
, empathy_chat_window
, GTK_TYPE_BIN
)
177 chat_window_accel_cb (GtkAccelGroup
*accelgroup
,
181 EmpathyChatWindow
*self
)
186 for (i
= 0; i
< G_N_ELEMENTS (tab_accel_keys
); i
++)
188 if (tab_accel_keys
[i
] == key
)
196 gtk_notebook_set_current_page (GTK_NOTEBOOK (self
->priv
->notebook
), num
);
199 static EmpathyChatWindow
*
200 chat_window_find_chat (EmpathyChat
*chat
)
204 for (l
= chat_windows
; l
; l
= l
->next
)
206 EmpathyChatWindow
*window
= l
->data
;
208 ll
= g_list_find (window
->priv
->chats
, chat
);
217 remove_all_chats (EmpathyChatWindow
*self
)
221 while (self
->priv
->chats
)
222 empathy_chat_window_remove_chat (self
, self
->priv
->chats
->data
);
224 g_object_unref (self
);
228 confirm_close_response_cb (GtkWidget
*dialog
,
230 EmpathyChatWindow
*window
)
234 chat
= g_object_get_data (G_OBJECT (dialog
), "chat");
236 gtk_widget_destroy (dialog
);
238 if (response
!= GTK_RESPONSE_ACCEPT
)
242 empathy_chat_window_remove_chat (window
, chat
);
244 remove_all_chats (window
);
248 confirm_close (EmpathyChatWindow
*self
,
249 gboolean close_window
,
254 gchar
*primary
, *secondary
;
256 g_return_if_fail (n_rooms
> 0);
259 g_return_if_fail (chat
== NULL
);
261 g_return_if_fail (chat
!= NULL
);
263 /* If there are no chats in this window, how could we possibly have got
266 g_return_if_fail (self
->priv
->chats
!= NULL
);
268 /* Treat closing a window which only has one tab exactly like closing
271 if (close_window
&& self
->priv
->chats
->next
== NULL
)
273 close_window
= FALSE
;
274 chat
= self
->priv
->chats
->data
;
279 primary
= g_strdup (_("Close this window?"));
283 gchar
*chat_name
= empathy_chat_dup_name (chat
);
284 secondary
= g_strdup_printf (
285 _("Closing this window will leave %s. You will "
286 "not receive any further messages until you "
293 secondary
= g_strdup_printf (
295 /* Note to translators: the number of chats will
296 * always be at least 2.
298 "Closing this window will leave a chat room. You will "
299 "not receive any further messages until you rejoin it.",
300 "Closing this window will leave %u chat rooms. You will "
301 "not receive any further messages until you rejoin them.",
308 gchar
*chat_name
= empathy_chat_dup_name (chat
);
309 primary
= g_strdup_printf (_("Leave %s?"), chat_name
);
310 secondary
= g_strdup (
311 _("You will not receive any further messages from this chat "
312 "room until you rejoin it."));
316 dialog
= gtk_message_dialog_new (
317 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self
))),
318 GTK_DIALOG_MODAL
| GTK_DIALOG_DESTROY_WITH_PARENT
,
323 gtk_window_set_title (GTK_WINDOW (dialog
), "");
324 g_object_set (dialog
, "secondary-text", secondary
, NULL
);
329 gtk_dialog_add_button (GTK_DIALOG (dialog
),
330 close_window
? _("Close window") : _("Leave room"),
331 GTK_RESPONSE_ACCEPT
);
332 gtk_dialog_set_default_response (GTK_DIALOG (dialog
),
333 GTK_RESPONSE_ACCEPT
);
336 g_object_set_data (G_OBJECT (dialog
), "chat", chat
);
338 g_signal_connect (dialog
, "response",
339 G_CALLBACK (confirm_close_response_cb
), self
);
341 gtk_window_present (GTK_WINDOW (dialog
));
344 /* Returns TRUE if we should check if the user really wants to leave. If it's
345 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
346 * the user is actually in the room as opposed to having been kicked or gone
347 * offline or something), then we should check.
350 chat_needs_close_confirmation (EmpathyChat
*chat
)
352 return (empathy_chat_is_room (chat
) &&
353 empathy_chat_get_tp_chat (chat
) != NULL
);
357 maybe_close_chat (EmpathyChatWindow
*window
,
360 g_return_if_fail (chat
!= NULL
);
362 if (chat_needs_close_confirmation (chat
))
363 confirm_close (window
, FALSE
, 1, chat
);
365 empathy_chat_window_remove_chat (window
, chat
);
369 chat_window_close_clicked_cb (GtkAction
*action
,
372 EmpathyChatWindow
*window
;
374 window
= chat_window_find_chat (chat
);
375 maybe_close_chat (window
, chat
);
379 chat_tab_style_updated_cb (GtkWidget
*hbox
,
383 int char_width
, h
, w
;
384 GtkStyleContext
*style_context
;
385 PangoContext
*pango_context
;
386 PangoFontDescription
*font_desc
;
387 PangoFontMetrics
*metrics
;
389 button
= g_object_get_data (G_OBJECT (user_data
),
390 "chat-window-tab-close-button");
391 style_context
= gtk_widget_get_style_context (hbox
);
392 pango_context
= gtk_widget_get_pango_context (hbox
);
394 gtk_style_context_save (style_context
);
395 gtk_style_context_set_state (style_context
, GTK_STATE_FLAG_NORMAL
);
396 gtk_style_context_get (style_context
,
397 GTK_STATE_FLAG_NORMAL
,
400 gtk_style_context_restore (style_context
);
402 metrics
= pango_context_get_metrics (pango_context
, font_desc
,
403 pango_context_get_language (pango_context
));
404 char_width
= pango_font_metrics_get_approximate_char_width (metrics
);
405 pango_font_metrics_unref (metrics
);
407 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button
),
408 GTK_ICON_SIZE_MENU
, &w
, &h
);
410 /* Request at least about 12 chars width plus at least space for the status
411 * image and the close button */
412 gtk_widget_set_size_request (hbox
,
413 12 * PANGO_PIXELS (char_width
) + 2 * w
, -1);
415 gtk_widget_set_size_request (button
, w
, h
);
416 pango_font_description_free (font_desc
);
420 create_close_button (void)
422 GtkWidget
*button
, *image
;
423 GtkStyleContext
*context
;
425 button
= gtk_button_new ();
427 context
= gtk_widget_get_style_context (button
);
428 gtk_style_context_add_class (context
, "empathy-tab-close-button");
430 gtk_button_set_relief (GTK_BUTTON (button
), GTK_RELIEF_NONE
);
431 gtk_button_set_focus_on_click (GTK_BUTTON (button
), FALSE
);
433 /* We don't want focus/keynav for the button to avoid clutter, and
434 * Ctrl-W works anyway.
436 gtk_widget_set_can_focus (button
, FALSE
);
437 gtk_widget_set_can_default (button
, FALSE
);
439 image
= gtk_image_new_from_icon_name ("window-close-symbolic",
441 gtk_widget_show (image
);
443 gtk_container_add (GTK_CONTAINER (button
), image
);
449 chat_window_create_label (EmpathyChatWindow
*window
,
451 gboolean is_tab_label
)
454 GtkWidget
*name_label
;
455 GtkWidget
*status_image
;
456 GtkWidget
*event_box
;
457 GtkWidget
*event_box_hbox
;
458 PangoAttrList
*attr_list
;
459 PangoAttribute
*attr
;
461 /* The spacing between the button and the label. */
462 hbox
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
, 0);
464 event_box
= gtk_event_box_new ();
465 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box
), FALSE
);
467 name_label
= gtk_label_new (NULL
);
469 gtk_label_set_ellipsize (GTK_LABEL (name_label
), PANGO_ELLIPSIZE_END
);
471 attr_list
= pango_attr_list_new ();
472 attr
= pango_attr_scale_new (1/1.2);
473 attr
->start_index
= 0;
474 attr
->end_index
= -1;
475 pango_attr_list_insert (attr_list
, attr
);
476 gtk_label_set_attributes (GTK_LABEL (name_label
), attr_list
);
477 pango_attr_list_unref (attr_list
);
479 gtk_misc_set_padding (GTK_MISC (name_label
), 2, 0);
480 gtk_misc_set_alignment (GTK_MISC (name_label
), 0.0, 0.5);
481 g_object_set_data (G_OBJECT (chat
),
482 is_tab_label
? "chat-window-tab-label" : "chat-window-menu-label",
485 status_image
= gtk_image_new ();
487 /* Spacing between the icon and label. */
488 event_box_hbox
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
, 0);
490 gtk_box_pack_start (GTK_BOX (event_box_hbox
), status_image
, FALSE
, FALSE
, 0);
491 gtk_box_pack_start (GTK_BOX (event_box_hbox
), name_label
, TRUE
, TRUE
, 0);
493 g_object_set_data (G_OBJECT (chat
),
494 is_tab_label
? "chat-window-tab-image" : "chat-window-menu-image",
496 g_object_set_data (G_OBJECT (chat
),
497 is_tab_label
? "chat-window-tab-tooltip-widget" :
498 "chat-window-menu-tooltip-widget",
501 gtk_container_add (GTK_CONTAINER (event_box
), event_box_hbox
);
502 gtk_box_pack_start (GTK_BOX (hbox
), event_box
, TRUE
, TRUE
, 0);
506 GtkWidget
*close_button
;
507 GtkWidget
*sending_spinner
;
509 sending_spinner
= gtk_spinner_new ();
511 gtk_box_pack_start (GTK_BOX (hbox
), sending_spinner
,
513 g_object_set_data (G_OBJECT (chat
),
514 "chat-window-tab-sending-spinner",
517 close_button
= create_close_button ();
518 g_object_set_data (G_OBJECT (chat
), "chat-window-tab-close-button",
521 gtk_box_pack_end (GTK_BOX (hbox
), close_button
, FALSE
, FALSE
, 0);
523 g_signal_connect (close_button
,
525 G_CALLBACK (chat_window_close_clicked_cb
), chat
);
527 /* React to theme changes and also setup the size correctly. */
528 g_signal_connect (hbox
, "style-updated",
529 G_CALLBACK (chat_tab_style_updated_cb
), chat
);
532 gtk_widget_show_all (hbox
);
538 _submenu_notify_visible_changed_cb (GObject
*object
,
542 g_signal_handlers_disconnect_by_func (object
,
543 _submenu_notify_visible_changed_cb
, userdata
);
545 chat_window_update (EMPATHY_CHAT_WINDOW (userdata
), TRUE
);
549 chat_window_menu_context_update (EmpathyChatWindow
*self
,
554 gboolean wrap_around
;
555 gboolean is_connected
;
558 page_num
= gtk_notebook_get_current_page (
559 GTK_NOTEBOOK (self
->priv
->notebook
));
560 first_page
= (page_num
== 0);
561 last_page
= (page_num
== (num_pages
- 1));
562 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
564 is_connected
= empathy_chat_get_tp_chat (self
->priv
->current_chat
) != NULL
;
566 gtk_action_set_sensitive (self
->priv
->menu_tabs_next
, (!last_page
||
568 gtk_action_set_sensitive (self
->priv
->menu_tabs_prev
, (!first_page
||
570 gtk_action_set_sensitive (self
->priv
->menu_tabs_detach
, num_pages
> 1);
571 gtk_action_set_sensitive (self
->priv
->menu_tabs_left
, !first_page
);
572 gtk_action_set_sensitive (self
->priv
->menu_tabs_right
, !last_page
);
573 gtk_action_set_sensitive (self
->priv
->menu_conv_insert_smiley
, is_connected
);
577 chat_window_conversation_menu_update (EmpathyChatWindow
*self
)
579 EmpathyTpChat
*tp_chat
;
580 TpConnection
*connection
;
582 gboolean sensitive
= FALSE
;
584 g_return_if_fail (self
->priv
->current_chat
!= NULL
);
586 action
= gtk_ui_manager_get_action (self
->priv
->ui_manager
,
587 "/chats_menubar/menu_conv/menu_conv_invite_participant");
588 tp_chat
= empathy_chat_get_tp_chat (self
->priv
->current_chat
);
592 connection
= tp_channel_get_connection (TP_CHANNEL (tp_chat
));
594 sensitive
= empathy_tp_chat_can_add_contact (tp_chat
) &&
595 (tp_connection_get_status (connection
, NULL
) ==
596 TP_CONNECTION_STATUS_CONNECTED
);
599 gtk_action_set_sensitive (action
, sensitive
);
603 chat_window_contact_menu_update (EmpathyChatWindow
*self
)
605 GtkWidget
*menu
, *submenu
, *orig_submenu
;
607 if (self
->priv
->current_chat
== NULL
)
610 if (self
->priv
->updating_menu
)
612 self
->priv
->updating_menu
= TRUE
;
614 menu
= gtk_ui_manager_get_widget (self
->priv
->ui_manager
,
615 "/chats_menubar/menu_contact");
616 orig_submenu
= gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu
));
618 if (orig_submenu
== NULL
|| !gtk_widget_get_visible (orig_submenu
))
620 submenu
= empathy_chat_get_contact_menu (self
->priv
->current_chat
);
624 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
625 g_object_set_data (G_OBJECT (submenu
), "window", self
);
627 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu
), submenu
);
628 gtk_widget_show (menu
);
629 gtk_widget_set_sensitive (menu
, TRUE
);
633 gtk_widget_set_sensitive (menu
, FALSE
);
638 tp_g_signal_connect_object (orig_submenu
,
640 (GCallback
)_submenu_notify_visible_changed_cb
, self
, 0);
643 self
->priv
->updating_menu
= FALSE
;
647 get_all_unread_messages (EmpathyChatWindow
*self
)
652 for (l
= self
->priv
->chats
; l
!= NULL
; l
= g_list_next (l
))
653 nb
+= empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l
->data
));
659 get_window_title_name (EmpathyChatWindow
*self
)
661 gchar
*active_name
, *ret
;
663 guint current_unread_msgs
;
665 nb_chats
= g_list_length (self
->priv
->chats
);
666 g_assert (nb_chats
> 0);
668 active_name
= empathy_chat_dup_name (self
->priv
->current_chat
);
670 current_unread_msgs
= empathy_chat_get_nb_unread_messages (
671 self
->priv
->current_chat
);
676 if (current_unread_msgs
== 0)
677 ret
= g_strdup (active_name
);
679 ret
= g_strdup_printf (ngettext (
681 "%s (%d unread)", current_unread_msgs
),
682 active_name
, current_unread_msgs
);
686 guint nb_others
= nb_chats
- 1;
687 guint all_unread_msgs
;
689 all_unread_msgs
= get_all_unread_messages (self
);
691 if (all_unread_msgs
== 0)
693 /* no unread message */
694 ret
= g_strdup_printf (ngettext (
696 "%s (and %u others)", nb_others
),
697 active_name
, nb_others
);
699 else if (all_unread_msgs
== current_unread_msgs
)
701 /* unread messages are in the current tab */
702 ret
= g_strdup_printf (ngettext (
704 "%s (%d unread)", current_unread_msgs
),
705 active_name
, current_unread_msgs
);
707 else if (current_unread_msgs
== 0)
709 /* unread messages are in other tabs */
710 ret
= g_strdup_printf (ngettext (
711 "%s (%d unread from others)",
712 "%s (%d unread from others)",
714 active_name
, all_unread_msgs
);
718 /* unread messages are in all the tabs */
719 ret
= g_strdup_printf (ngettext (
720 "%s (%d unread from all)",
721 "%s (%d unread from all)",
723 active_name
, all_unread_msgs
);
727 g_free (active_name
);
733 chat_window_title_update (EmpathyChatWindow
*self
)
737 name
= get_window_title_name (self
);
738 //gtk_window_set_title (GTK_WINDOW (self), name);
743 chat_window_icon_update (EmpathyChatWindow
*self
,
744 gboolean new_messages
)
747 EmpathyContact
*remote_contact
;
748 gboolean avatar_in_icon
;
751 n_chats
= g_list_length (self
->priv
->chats
);
753 /* Update window icon */
756 //gtk_window_set_icon_name (GTK_WINDOW (self),
757 // EMPATHY_IMAGE_MESSAGE);
761 avatar_in_icon
= g_settings_get_boolean (self
->priv
->gsettings_chat
,
762 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON
);
764 if (n_chats
== 1 && avatar_in_icon
)
766 remote_contact
= empathy_chat_get_remote_contact (self
->priv
->current_chat
);
767 icon
= empathy_pixbuf_avatar_from_contact_scaled (remote_contact
,
769 //gtk_window_set_icon (GTK_WINDOW (self), icon);
772 g_object_unref (icon
);
776 //gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
782 chat_window_close_button_update (EmpathyChatWindow
*self
,
786 GtkWidget
*chat_close_button
;
791 chat
= gtk_notebook_get_nth_page (GTK_NOTEBOOK (self
->priv
->notebook
), 0);
792 chat_close_button
= g_object_get_data (G_OBJECT (chat
),
793 "chat-window-tab-close-button");
794 gtk_widget_hide (chat_close_button
);
798 for (i
=0; i
<num_pages
; i
++)
800 chat
= gtk_notebook_get_nth_page (GTK_NOTEBOOK (self
->priv
->notebook
), i
);
801 chat_close_button
= g_object_get_data (G_OBJECT (chat
),
802 "chat-window-tab-close-button");
803 gtk_widget_show (chat_close_button
);
809 chat_window_update (EmpathyChatWindow
*self
,
810 gboolean update_contact_menu
)
814 num_pages
= gtk_notebook_get_n_pages (GTK_NOTEBOOK (self
->priv
->notebook
));
816 /* Update Tab menu */
817 chat_window_menu_context_update (self
, num_pages
);
819 chat_window_conversation_menu_update (self
);
821 /* If this update is due to a focus-in event, we know the menu will be
822 the same as when we last left it, so no work to do. Besides, if we
823 swap out the menu on a focus-in, we may confuse any external global
825 if (update_contact_menu
)
827 chat_window_contact_menu_update (self
);
830 chat_window_title_update (self
);
832 chat_window_icon_update (self
, get_all_unread_messages (self
) > 0);
834 chat_window_close_button_update (self
, num_pages
);
838 append_markup_printf (GString
*string
,
845 va_start (args
, format
);
847 tmp
= g_markup_vprintf_escaped (format
, args
);
848 g_string_append (string
, tmp
);
855 chat_window_update_chat_tab_full (EmpathyChat
*chat
,
856 gboolean update_contact_menu
)
858 EmpathyChatWindow
*self
;
859 EmpathyContact
*remote_contact
;
863 const gchar
*subject
;
864 const gchar
*status
= NULL
;
868 const gchar
*icon_name
;
869 GtkWidget
*tab_image
;
870 GtkWidget
*menu_image
;
871 GtkWidget
*sending_spinner
;
874 self
= chat_window_find_chat (chat
);
878 /* Get information */
879 name
= empathy_chat_dup_name (chat
);
880 account
= empathy_chat_get_account (chat
);
881 subject
= empathy_chat_get_subject (chat
);
882 remote_contact
= empathy_chat_get_remote_contact (chat
);
884 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
886 name
, tp_proxy_get_object_path (account
), subject
, remote_contact
);
888 /* Update tab image */
889 if (empathy_chat_get_tp_chat (chat
) == NULL
)
891 /* No TpChat, we are disconnected */
894 else if (empathy_chat_get_nb_unread_messages (chat
) > 0)
896 icon_name
= EMPATHY_IMAGE_MESSAGE
;
898 else if (remote_contact
&& empathy_chat_is_composing (chat
))
900 icon_name
= EMPATHY_IMAGE_TYPING
;
902 else if (empathy_chat_is_sms_channel (chat
))
904 icon_name
= EMPATHY_IMAGE_SMS
;
906 else if (remote_contact
)
908 icon_name
= empathy_icon_name_for_contact (remote_contact
);
912 icon_name
= EMPATHY_IMAGE_GROUP_MESSAGE
;
915 tab_image
= g_object_get_data (G_OBJECT (chat
), "chat-window-tab-image");
916 menu_image
= g_object_get_data (G_OBJECT (chat
), "chat-window-menu-image");
918 if (icon_name
!= NULL
)
920 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image
), icon_name
,
922 gtk_widget_show (tab_image
);
923 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image
), icon_name
,
925 gtk_widget_show (menu_image
);
929 gtk_widget_hide (tab_image
);
930 gtk_widget_hide (menu_image
);
933 /* Update the sending spinner */
934 nb_sending
= empathy_chat_get_n_messages_sending (chat
);
935 sending_spinner
= g_object_get_data (G_OBJECT (chat
),
936 "chat-window-tab-sending-spinner");
938 g_object_set (sending_spinner
,
939 "active", nb_sending
> 0,
940 "visible", nb_sending
> 0,
943 /* Update tab tooltip */
944 tooltip
= g_string_new (NULL
);
948 id
= empathy_contact_get_id (remote_contact
);
949 status
= empathy_contact_get_presence_message (remote_contact
);
956 if (empathy_chat_is_sms_channel (chat
))
957 append_markup_printf (tooltip
, "%s ", _("SMS:"));
959 append_markup_printf (tooltip
, "<b>%s</b><small> (%s)</small>",
960 id
, tp_account_get_display_name (account
));
964 char *tmp
= g_strdup_printf (
965 ngettext ("Sending %d message",
966 "Sending %d messages",
970 g_string_append (tooltip
, "\n");
971 g_string_append (tooltip
, tmp
);
973 gtk_widget_set_tooltip_text (sending_spinner
, tmp
);
977 if (!TPAW_STR_EMPTY (status
))
978 append_markup_printf (tooltip
, "\n<i>%s</i>", status
);
980 if (!TPAW_STR_EMPTY (subject
))
981 append_markup_printf (tooltip
, "\n<b>%s</b> %s",
982 _("Topic:"), subject
);
984 if (remote_contact
&& empathy_chat_is_composing (chat
))
985 append_markup_printf (tooltip
, "\n%s", _("Typing a message."));
987 if (remote_contact
!= NULL
)
989 const gchar
* const *types
;
991 types
= empathy_contact_get_client_types (remote_contact
);
992 if (empathy_client_types_contains_mobile_device ((GStrv
) types
))
994 /* I'm on a mobile device ! */
997 name
= g_strdup_printf ("☎ %s", name
);
1002 markup
= g_string_free (tooltip
, FALSE
);
1003 widget
= g_object_get_data (G_OBJECT (chat
),
1004 "chat-window-tab-tooltip-widget");
1005 gtk_widget_set_tooltip_markup (widget
, markup
);
1007 widget
= g_object_get_data (G_OBJECT (chat
),
1008 "chat-window-menu-tooltip-widget");
1009 gtk_widget_set_tooltip_markup (widget
, markup
);
1012 /* Update tab and menu label */
1013 if (empathy_chat_is_highlighted (chat
))
1015 markup
= g_markup_printf_escaped (
1016 "<span color=\"red\" weight=\"bold\">%s</span>",
1021 markup
= g_markup_escape_text (name
, -1);
1024 widget
= g_object_get_data (G_OBJECT (chat
), "chat-window-tab-label");
1025 gtk_label_set_markup (GTK_LABEL (widget
), markup
);
1026 widget
= g_object_get_data (G_OBJECT (chat
), "chat-window-menu-label");
1027 gtk_label_set_markup (GTK_LABEL (widget
), markup
);
1030 /* Update the window if it's the current chat */
1031 if (self
->priv
->current_chat
== chat
)
1032 chat_window_update (self
, update_contact_menu
);
1038 chat_window_update_chat_tab (EmpathyChat
*chat
)
1040 chat_window_update_chat_tab_full (chat
, TRUE
);
1044 chat_window_chat_notify_cb (EmpathyChat
*chat
)
1046 EmpathyChatWindow
*window
;
1047 EmpathyContact
*old_remote_contact
;
1048 EmpathyContact
*remote_contact
= NULL
;
1050 old_remote_contact
= g_object_get_data (G_OBJECT (chat
),
1051 "chat-window-remote-contact");
1052 remote_contact
= empathy_chat_get_remote_contact (chat
);
1054 if (old_remote_contact
!= remote_contact
)
1056 /* The remote-contact associated with the chat changed, we need
1057 * to keep track of any change of that contact and update the
1058 * window each time. */
1060 g_signal_connect_swapped (remote_contact
, "notify",
1061 G_CALLBACK (chat_window_update_chat_tab
), chat
);
1063 if (old_remote_contact
)
1064 g_signal_handlers_disconnect_by_func (old_remote_contact
,
1065 chat_window_update_chat_tab
, chat
);
1067 g_object_set_data_full (G_OBJECT (chat
), "chat-window-remote-contact",
1068 g_object_ref (remote_contact
), (GDestroyNotify
) g_object_unref
);
1071 chat_window_update_chat_tab (chat
);
1073 window
= chat_window_find_chat (chat
);
1075 chat_window_update (window
, FALSE
);
1079 chat_window_insert_smiley_activate_cb (EmpathySmileyManager
*manager
,
1080 EmpathySmiley
*smiley
,
1083 EmpathyChatWindow
*self
= user_data
;
1085 GtkTextBuffer
*buffer
;
1087 chat
= self
->priv
->current_chat
;
1088 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1090 empathy_chat_insert_smiley (buffer
, smiley
);
1094 chat_window_conv_activate_cb (GtkAction
*action
,
1095 EmpathyChatWindow
*self
)
1099 EmpathyContact
*remote_contact
= NULL
;
1100 gboolean disconnected
;
1102 /* Favorite room menu */
1103 is_room
= empathy_chat_is_room (self
->priv
->current_chat
);
1108 gboolean found
= FALSE
;
1109 EmpathyChatroom
*chatroom
;
1111 room
= empathy_chat_get_id (self
->priv
->current_chat
);
1112 account
= empathy_chat_get_account (self
->priv
->current_chat
);
1113 chatroom
= empathy_chatroom_manager_find (self
->priv
->chatroom_manager
,
1116 if (chatroom
!= NULL
)
1117 found
= empathy_chatroom_is_favorite (chatroom
);
1119 DEBUG ("This room %s favorite", found
? "is" : "is not");
1120 gtk_toggle_action_set_active (
1121 GTK_TOGGLE_ACTION (self
->priv
->menu_conv_favorite
), found
);
1123 if (chatroom
!= NULL
)
1124 found
= empathy_chatroom_is_always_urgent (chatroom
);
1126 gtk_toggle_action_set_active (
1127 GTK_TOGGLE_ACTION (self
->priv
->menu_conv_always_urgent
), found
);
1130 gtk_action_set_visible (self
->priv
->menu_conv_favorite
, is_room
);
1131 gtk_action_set_visible (self
->priv
->menu_conv_always_urgent
, is_room
);
1133 /* Show contacts menu */
1134 g_object_get (self
->priv
->current_chat
,
1135 "remote-contact", &remote_contact
,
1136 "show-contacts", &active
,
1139 if (remote_contact
== NULL
)
1141 gtk_toggle_action_set_active (
1142 GTK_TOGGLE_ACTION (self
->priv
->menu_conv_toggle_contacts
), active
);
1145 /* Menu-items to be visible for MUCs only */
1146 gtk_action_set_visible (self
->priv
->menu_conv_toggle_contacts
,
1147 (remote_contact
== NULL
));
1149 disconnected
= (empathy_chat_get_tp_chat (self
->priv
->current_chat
) == NULL
);
1152 gtk_action_set_visible (self
->priv
->menu_conv_join_chat
, TRUE
);
1153 gtk_action_set_visible (self
->priv
->menu_conv_leave_chat
, FALSE
);
1157 TpChannel
*channel
= NULL
;
1158 TpContact
*self_contact
= NULL
;
1159 TpHandle self_handle
= 0;
1161 channel
= (TpChannel
*) (empathy_chat_get_tp_chat (
1162 self
->priv
->current_chat
));
1163 self_contact
= tp_channel_group_get_self_contact (channel
);
1164 if (self_contact
== NULL
)
1166 /* The channel may not be a group */
1167 gtk_action_set_visible (self
->priv
->menu_conv_leave_chat
, FALSE
);
1171 self_handle
= tp_contact_get_handle (self_contact
);
1172 /* There is sometimes a lag between the members-changed signal
1173 emitted on tp-chat and invalidated signal being emitted on the channel.
1174 Leave Chat menu-item should be sensitive only till our self-handle is
1175 a part of channel-members */
1176 gtk_action_set_visible (self
->priv
->menu_conv_leave_chat
,
1180 /* Join Chat is insensitive for a connected chat */
1181 gtk_action_set_visible (self
->priv
->menu_conv_join_chat
, FALSE
);
1184 if (remote_contact
!= NULL
)
1185 g_object_unref (remote_contact
);
1189 chat_window_clear_activate_cb (GtkAction
*action
,
1190 EmpathyChatWindow
*self
)
1192 empathy_chat_clear (self
->priv
->current_chat
);
1196 chat_window_favorite_toggled_cb (GtkToggleAction
*toggle_action
,
1197 EmpathyChatWindow
*self
)
1203 EmpathyChatroom
*chatroom
;
1205 active
= gtk_toggle_action_get_active (toggle_action
);
1206 account
= empathy_chat_get_account (self
->priv
->current_chat
);
1207 room
= empathy_chat_get_id (self
->priv
->current_chat
);
1208 name
= empathy_chat_dup_name (self
->priv
->current_chat
);
1210 chatroom
= empathy_chatroom_manager_ensure_chatroom (self
->priv
->chatroom_manager
,
1211 account
, room
, name
);
1213 empathy_chatroom_set_favorite (chatroom
, active
);
1214 g_object_unref (chatroom
);
1219 chat_window_always_urgent_toggled_cb (GtkToggleAction
*toggle_action
,
1220 EmpathyChatWindow
*self
)
1226 EmpathyChatroom
*chatroom
;
1228 active
= gtk_toggle_action_get_active (toggle_action
);
1229 account
= empathy_chat_get_account (self
->priv
->current_chat
);
1230 room
= empathy_chat_get_id (self
->priv
->current_chat
);
1231 name
= empathy_chat_dup_name (self
->priv
->current_chat
);
1233 chatroom
= empathy_chatroom_manager_ensure_chatroom (self
->priv
->chatroom_manager
,
1234 account
, room
, name
);
1236 empathy_chatroom_set_always_urgent (chatroom
, active
);
1237 g_object_unref (chatroom
);
1242 chat_window_contacts_toggled_cb (GtkToggleAction
*toggle_action
,
1243 EmpathyChatWindow
*self
)
1247 active
= gtk_toggle_action_get_active (toggle_action
);
1249 empathy_chat_set_show_contacts (self
->priv
->current_chat
, active
);
1253 chat_window_invite_participant_activate_cb (GtkAction
*action
,
1254 EmpathyChatWindow
*self
)
1257 EmpathyTpChat
*tp_chat
;
1260 g_return_if_fail (self
->priv
->current_chat
!= NULL
);
1262 tp_chat
= empathy_chat_get_tp_chat (self
->priv
->current_chat
);
1264 dialog
= empathy_invite_participant_dialog_new (
1265 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self
))), tp_chat
);
1267 gtk_widget_show (dialog
);
1269 response
= gtk_dialog_run (GTK_DIALOG (dialog
));
1271 if (response
== GTK_RESPONSE_ACCEPT
)
1273 TpContact
*tp_contact
;
1274 EmpathyContact
*contact
;
1276 tp_contact
= empathy_invite_participant_dialog_get_selected (
1277 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog
));
1278 if (tp_contact
== NULL
)
1281 contact
= empathy_contact_dup_from_tp_contact (tp_contact
);
1283 empathy_tp_chat_add (tp_chat
, contact
, _("Inviting you to this room"));
1285 g_object_unref (contact
);
1289 gtk_widget_destroy (dialog
);
1293 chat_window_join_chat_activate_cb (GtkAction
*action
,
1294 EmpathyChatWindow
*self
)
1296 g_return_if_fail (self
->priv
->current_chat
!= NULL
);
1298 empathy_chat_join_muc (self
->priv
->current_chat
,
1299 empathy_chat_get_id (self
->priv
->current_chat
));
1303 chat_window_leave_chat_activate_cb (GtkAction
*action
,
1304 EmpathyChatWindow
*self
)
1306 EmpathyTpChat
* tp_chat
;
1308 g_return_if_fail (self
->priv
->current_chat
!= NULL
);
1310 tp_chat
= empathy_chat_get_tp_chat (self
->priv
->current_chat
);
1311 if (tp_chat
!= NULL
)
1312 empathy_tp_chat_leave (tp_chat
, "");
1316 chat_window_close_activate_cb (GtkAction
*action
,
1317 EmpathyChatWindow
*self
)
1319 g_return_if_fail (self
->priv
->current_chat
!= NULL
);
1321 maybe_close_chat (self
, self
->priv
->current_chat
);
1325 theme_adium_can_copy_cb (EmpathyThemeAdium
*view
,
1326 GAsyncResult
*result
,
1327 EmpathyChatWindow
*self
)
1329 gtk_action_set_sensitive (self
->priv
->menu_edit_copy
,
1330 empathy_theme_adium_can_copy_finish (view
, result
, NULL
));
1334 chat_window_edit_activate_cb (GtkAction
*action
,
1335 EmpathyChatWindow
*self
)
1337 GtkClipboard
*clipboard
;
1338 GtkTextBuffer
*buffer
;
1339 gboolean text_available
;
1341 g_return_if_fail (self
->priv
->current_chat
!= NULL
);
1343 if (!empathy_chat_get_tp_chat (self
->priv
->current_chat
))
1345 gtk_action_set_sensitive (self
->priv
->menu_edit_copy
, FALSE
);
1346 gtk_action_set_sensitive (self
->priv
->menu_edit_cut
, FALSE
);
1347 gtk_action_set_sensitive (self
->priv
->menu_edit_paste
, FALSE
);
1351 buffer
= gtk_text_view_get_buffer (
1352 GTK_TEXT_VIEW (self
->priv
->current_chat
->input_text_view
));
1354 if (gtk_text_buffer_get_has_selection (buffer
))
1356 gtk_action_set_sensitive (self
->priv
->menu_edit_copy
, TRUE
);
1357 gtk_action_set_sensitive (self
->priv
->menu_edit_cut
, TRUE
);
1361 empathy_theme_adium_can_copy (self
->priv
->current_chat
->view
, NULL
,
1362 (GAsyncReadyCallback
)theme_adium_can_copy_cb
, self
);
1363 gtk_action_set_sensitive (self
->priv
->menu_edit_cut
, FALSE
);
1366 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
1367 text_available
= gtk_clipboard_wait_is_text_available (clipboard
);
1368 gtk_action_set_sensitive (self
->priv
->menu_edit_paste
, text_available
);
1372 chat_window_cut_activate_cb (GtkAction
*action
,
1373 EmpathyChatWindow
*self
)
1375 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self
));
1377 empathy_chat_cut (self
->priv
->current_chat
);
1381 chat_window_copy_activate_cb (GtkAction
*action
,
1382 EmpathyChatWindow
*self
)
1384 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self
));
1386 empathy_chat_copy (self
->priv
->current_chat
);
1390 chat_window_paste_activate_cb (GtkAction
*action
,
1391 EmpathyChatWindow
*self
)
1393 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self
));
1395 empathy_chat_paste (self
->priv
->current_chat
);
1399 chat_window_find_activate_cb (GtkAction
*action
,
1400 EmpathyChatWindow
*self
)
1402 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self
));
1404 empathy_chat_find (self
->priv
->current_chat
);
1408 chat_window_tabs_next_activate_cb (GtkAction
*action
,
1409 EmpathyChatWindow
*self
)
1411 gint index_
, numPages
;
1412 gboolean wrap_around
;
1414 g_object_get (gtk_settings_get_default (),
1415 "gtk-keynav-wrap-around", &wrap_around
,
1418 index_
= gtk_notebook_get_current_page (GTK_NOTEBOOK (self
->priv
->notebook
));
1419 numPages
= gtk_notebook_get_n_pages (GTK_NOTEBOOK (self
->priv
->notebook
));
1421 if (index_
== (numPages
- 1) && wrap_around
)
1423 gtk_notebook_set_current_page (GTK_NOTEBOOK (self
->priv
->notebook
), 0);
1427 gtk_notebook_next_page (GTK_NOTEBOOK (self
->priv
->notebook
));
1431 chat_window_tabs_previous_activate_cb (GtkAction
*action
,
1432 EmpathyChatWindow
*self
)
1434 gint index_
, numPages
;
1435 gboolean wrap_around
;
1437 g_object_get (gtk_settings_get_default (),
1438 "gtk-keynav-wrap-around", &wrap_around
,
1441 index_
= gtk_notebook_get_current_page (GTK_NOTEBOOK (self
->priv
->notebook
));
1442 numPages
= gtk_notebook_get_n_pages (GTK_NOTEBOOK (self
->priv
->notebook
));
1444 if (index_
<= 0 && wrap_around
)
1446 gtk_notebook_set_current_page (GTK_NOTEBOOK (self
->priv
->notebook
),
1451 gtk_notebook_prev_page (GTK_NOTEBOOK (self
->priv
->notebook
));
1455 empathy_chat_window_next_tab (EmpathyChatWindow
*self
)
1457 chat_window_tabs_next_activate_cb (NULL
, self
);
1461 empathy_chat_window_prev_tab (EmpathyChatWindow
*self
)
1463 chat_window_tabs_previous_activate_cb (NULL
, self
);
1468 chat_window_tabs_undo_close_tab_activate_cb (GtkAction
*action
,
1469 EmpathyChatWindow
*self
)
1471 empathy_chat_manager_undo_closed_chat (self
->priv
->chat_manager
,
1472 empathy_get_current_action_time ());
1476 chat_window_tabs_left_activate_cb (GtkAction
*action
,
1477 EmpathyChatWindow
*self
)
1480 gint index_
, num_pages
;
1482 chat
= self
->priv
->current_chat
;
1483 index_
= gtk_notebook_get_current_page (GTK_NOTEBOOK (self
->priv
->notebook
));
1487 gtk_notebook_reorder_child (GTK_NOTEBOOK (self
->priv
->notebook
), GTK_WIDGET (chat
),
1490 num_pages
= gtk_notebook_get_n_pages (GTK_NOTEBOOK (self
->priv
->notebook
));
1491 chat_window_menu_context_update (self
, num_pages
);
1495 chat_window_tabs_right_activate_cb (GtkAction
*action
,
1496 EmpathyChatWindow
*self
)
1499 gint index_
, num_pages
;
1501 chat
= self
->priv
->current_chat
;
1502 index_
= gtk_notebook_get_current_page (GTK_NOTEBOOK (self
->priv
->notebook
));
1504 gtk_notebook_reorder_child (GTK_NOTEBOOK (self
->priv
->notebook
), GTK_WIDGET (chat
),
1507 num_pages
= gtk_notebook_get_n_pages (GTK_NOTEBOOK (self
->priv
->notebook
));
1508 chat_window_menu_context_update (self
, num_pages
);
1512 empathy_chat_window_new (void)
1514 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW
,
1519 chat_window_detach_activate_cb (GtkAction
*action
,
1520 EmpathyChatWindow
*self
)
1522 EmpathyChatWindow
*new_window
;
1525 chat
= self
->priv
->current_chat
;
1526 new_window
= empathy_chat_window_new ();
1528 empathy_chat_window_move_chat (self
, new_window
, chat
);
1530 gtk_widget_show (GTK_WIDGET (new_window
));
1534 chat_window_help_contents_activate_cb (GtkAction
*action
,
1535 EmpathyChatWindow
*self
)
1537 empathy_url_show (GTK_WIDGET (self
), "help:empathy");
1541 chat_window_help_about_activate_cb (GtkAction
*action
,
1542 EmpathyChatWindow
*self
)
1544 empathy_about_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self
))));
1548 chat_window_delete_event_cb (GtkWidget
*dialog
,
1550 EmpathyChatWindow
*self
)
1552 EmpathyChat
*chat
= NULL
;
1556 DEBUG ("Delete event received");
1558 for (l
= self
->priv
->chats
; l
!= NULL
; l
= l
->next
)
1560 if (chat_needs_close_confirmation (l
->data
))
1569 confirm_close (self
, TRUE
, n_rooms
, (n_rooms
== 1 ? chat
: NULL
));
1573 remove_all_chats (self
);
1580 chat_window_composing_cb (EmpathyChat
*chat
,
1581 gboolean is_composing
,
1582 EmpathyChatWindow
*self
)
1584 chat_window_update_chat_tab (chat
);
1588 chat_window_set_urgency_hint (EmpathyChatWindow
*self
,
1591 //gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1595 chat_window_notification_closed_cb (NotifyNotification
*notify
,
1596 EmpathyChatWindow
*self
)
1598 g_object_unref (notify
);
1599 if (self
->priv
->notification
== notify
)
1600 self
->priv
->notification
= NULL
;
1604 chat_window_show_or_update_notification (EmpathyChatWindow
*self
,
1605 EmpathyMessage
*message
,
1608 EmpathyContact
*sender
;
1609 const gchar
*header
;
1613 gboolean res
, has_x_canonical_append
;
1614 NotifyNotification
*notification
= self
->priv
->notification
;
1616 if (!empathy_notify_manager_notification_is_enabled (self
->priv
->notify_mgr
))
1619 res
= g_settings_get_boolean (self
->priv
->gsettings_notif
,
1620 EMPATHY_PREFS_NOTIFICATIONS_FOCUS
);
1625 sender
= empathy_message_get_sender (message
);
1626 header
= empathy_contact_get_alias (sender
);
1627 body
= empathy_message_get_body (message
);
1628 escaped
= g_markup_escape_text (body
, -1);
1630 has_x_canonical_append
= empathy_notify_manager_has_capability (
1631 self
->priv
->notify_mgr
, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND
);
1633 if (notification
!= NULL
&& !has_x_canonical_append
)
1635 /* if the notification server supports x-canonical-append, it is
1636 better to not use notify_notification_update to avoid
1637 overwriting the current notification message */
1638 notify_notification_update (notification
,
1639 header
, escaped
, NULL
);
1643 /* if the notification server supports x-canonical-append,
1644 the hint will be added, so that the message from the
1645 just created notification will be automatically appended
1646 to an existing notification with the same title.
1647 In this way the previous message will not be lost: the new
1648 message will appear below it, in the same notification */
1649 const gchar
*category
= empathy_chat_is_room (chat
)
1650 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1651 : EMPATHY_NOTIFICATION_CATEGORY_CHAT
;
1653 notification
= empathy_notify_manager_create_notification (header
,
1656 if (self
->priv
->notification
== NULL
)
1657 self
->priv
->notification
= notification
;
1659 tp_g_signal_connect_object (notification
, "closed",
1660 G_CALLBACK (chat_window_notification_closed_cb
), self
, 0);
1662 if (has_x_canonical_append
)
1664 /* We have to set a not empty string to keep libnotify happy */
1665 notify_notification_set_hint_string (notification
,
1666 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND
, "1");
1669 notify_notification_set_hint (notification
,
1670 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY
, g_variant_new_string (category
));
1673 pixbuf
= empathy_notify_manager_get_pixbuf_for_notification (self
->priv
->notify_mgr
,
1674 sender
, EMPATHY_IMAGE_NEW_MESSAGE
);
1678 notify_notification_set_icon_from_pixbuf (notification
, pixbuf
);
1679 g_object_unref (pixbuf
);
1682 notify_notification_show (notification
, NULL
);
1688 empathy_chat_window_has_focus (EmpathyChatWindow
*self
)
1692 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self
), FALSE
);
1694 g_object_get ( gtk_widget_get_toplevel (GTK_WIDGET (self
)), "has-toplevel-focus", &has_focus
, NULL
);
1700 chat_window_new_message_cb (EmpathyChat
*chat
,
1701 EmpathyMessage
*message
,
1703 gboolean should_highlight
,
1704 EmpathyChatWindow
*self
)
1707 gboolean needs_urgency
;
1708 EmpathyContact
*sender
;
1710 has_focus
= empathy_chat_window_has_focus (self
);
1712 /* - if we're the sender, we play the sound if it's specified in the
1713 * preferences and we're not away.
1714 * - if we receive a message, we play the sound if it's specified in the
1715 * preferences and the window does not have focus on the chat receiving
1719 sender
= empathy_message_get_sender (message
);
1721 if (empathy_contact_is_user (sender
))
1723 empathy_sound_manager_play (self
->priv
->sound_mgr
, GTK_WIDGET (self
),
1724 EMPATHY_SOUND_MESSAGE_OUTGOING
);
1728 if (has_focus
&& self
->priv
->current_chat
== chat
)
1730 /* window and tab are focused so consider the message to be read */
1732 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1733 empathy_chat_messages_read (chat
);
1737 /* Update the chat tab if this is the first unread message */
1738 if (empathy_chat_get_nb_unread_messages (chat
) == 1)
1740 chat_window_update_chat_tab (chat
);
1743 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1744 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1745 * an unamed MUC (msn-like).
1746 * In case of a MUC, we set urgency if either:
1747 * a) the chatroom's always_urgent property is TRUE
1748 * b) the message contains our alias
1750 if (empathy_chat_is_room (chat
))
1754 EmpathyChatroom
*chatroom
;
1756 account
= empathy_chat_get_account (chat
);
1757 room
= empathy_chat_get_id (chat
);
1759 chatroom
= empathy_chatroom_manager_find (self
->priv
->chatroom_manager
,
1762 if (chatroom
!= NULL
&& empathy_chatroom_is_always_urgent (chatroom
))
1763 needs_urgency
= TRUE
;
1765 needs_urgency
= should_highlight
;
1769 needs_urgency
= TRUE
;
1775 chat_window_set_urgency_hint (self
, TRUE
);
1777 /* Pending messages have already been displayed and notified in the
1778 * approver, so we don't display a notification and play a sound
1782 empathy_sound_manager_play (self
->priv
->sound_mgr
,
1783 GTK_WIDGET (self
), EMPATHY_SOUND_MESSAGE_INCOMING
);
1785 chat_window_show_or_update_notification (self
, message
, chat
);
1789 /* update the number of unread messages and the window icon */
1790 chat_window_title_update (self
);
1791 chat_window_icon_update (self
, TRUE
);
1795 chat_window_command_part (EmpathyChat
*chat
,
1798 EmpathyChat
*chat_to_be_parted
;
1799 EmpathyTpChat
*tp_chat
= NULL
;
1801 if (strv
[1] == NULL
)
1803 /* No chatroom ID specified */
1804 tp_chat
= empathy_chat_get_tp_chat (chat
);
1807 empathy_tp_chat_leave (tp_chat
, "");
1812 chat_to_be_parted
= empathy_chat_window_find_chat (
1813 empathy_chat_get_account (chat
), strv
[1], FALSE
);
1815 if (chat_to_be_parted
!= NULL
)
1817 /* Found a chatroom matching the specified ID */
1818 tp_chat
= empathy_chat_get_tp_chat (chat_to_be_parted
);
1821 empathy_tp_chat_leave (tp_chat
, strv
[2]);
1827 /* Going by the syntax of PART command:
1829 * /PART [<chatroom-ID>] [<reason>]
1831 * Chatroom-ID is not a must to specify a reason.
1832 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1833 * MUC then the current chatroom should be parted and srtv[1] should
1834 * be treated as part of the optional part-message. */
1835 message
= g_strconcat (strv
[1], " ", strv
[2], NULL
);
1836 tp_chat
= empathy_chat_get_tp_chat (chat
);
1839 empathy_tp_chat_leave (tp_chat
, message
);
1845 static GtkNotebook
*
1846 notebook_create_window_cb (GtkNotebook
*source
,
1852 EmpathyChatWindow
*window
, *new_window
;
1855 chat
= EMPATHY_CHAT (page
);
1856 window
= chat_window_find_chat (chat
);
1858 new_window
= empathy_chat_window_new ();
1860 DEBUG ("Detach hook called");
1862 empathy_chat_window_move_chat (window
, new_window
, chat
);
1864 gtk_widget_show (GTK_WIDGET (new_window
));
1865 //gtk_window_move (GTK_WINDOW (new_window), x, y);
1871 chat_window_page_switched_cb (GtkNotebook
*notebook
,
1874 EmpathyChatWindow
*self
)
1876 EmpathyChat
*chat
= EMPATHY_CHAT (child
);
1878 DEBUG ("Page switched");
1880 if (self
->priv
->page_added
)
1882 self
->priv
->page_added
= FALSE
;
1883 empathy_chat_scroll_down (chat
);
1885 else if (self
->priv
->current_chat
== chat
)
1890 self
->priv
->current_chat
= chat
;
1891 empathy_chat_messages_read (chat
);
1893 chat_window_update_chat_tab (chat
);
1897 chat_window_page_added_cb (GtkNotebook
*notebook
,
1900 EmpathyChatWindow
*self
)
1904 /* If we just received DND to the same window, we don't want
1905 * to do anything here like removing the tab and then readding
1906 * it, so we return here and in "page-added".
1908 if (self
->priv
->dnd_same_window
)
1910 DEBUG ("Page added (back to the same window)");
1911 self
->priv
->dnd_same_window
= FALSE
;
1915 DEBUG ("Page added");
1917 /* Get chat object */
1918 chat
= EMPATHY_CHAT (child
);
1920 /* Connect chat signals for this window */
1921 g_signal_connect (chat
, "composing",
1922 G_CALLBACK (chat_window_composing_cb
), self
);
1923 g_signal_connect (chat
, "new-message",
1924 G_CALLBACK (chat_window_new_message_cb
), self
);
1925 g_signal_connect (chat
, "part-command-entered",
1926 G_CALLBACK (chat_window_command_part
), NULL
);
1927 g_signal_connect (chat
, "notify::tp-chat",
1928 G_CALLBACK (chat_window_update_chat_tab
), self
);
1930 /* Set flag so we know to perform some special operations on
1931 * switch page due to the new page being added.
1933 self
->priv
->page_added
= TRUE
;
1935 /* Get list of chats up to date */
1936 self
->priv
->chats
= g_list_append (self
->priv
->chats
, chat
);
1938 chat_window_update_chat_tab (chat
);
1942 chat_window_page_removed_cb (GtkNotebook
*notebook
,
1945 EmpathyChatWindow
*self
)
1949 /* If we just received DND to the same window, we don't want
1950 * to do anything here like removing the tab and then readding
1951 * it, so we return here and in "page-added".
1953 if (self
->priv
->dnd_same_window
)
1955 DEBUG ("Page removed (and will be readded to same window)");
1959 DEBUG ("Page removed");
1961 /* Get chat object */
1962 chat
= EMPATHY_CHAT (child
);
1964 /* Disconnect all signal handlers for this chat and this window */
1965 g_signal_handlers_disconnect_by_func (chat
,
1966 G_CALLBACK (chat_window_composing_cb
), self
);
1967 g_signal_handlers_disconnect_by_func (chat
,
1968 G_CALLBACK (chat_window_new_message_cb
), self
);
1969 g_signal_handlers_disconnect_by_func (chat
,
1970 G_CALLBACK (chat_window_update_chat_tab
), self
);
1972 /* Keep list of chats up to date */
1973 self
->priv
->chats
= g_list_remove (self
->priv
->chats
, chat
);
1974 empathy_chat_messages_read (chat
);
1976 if (self
->priv
->chats
== NULL
)
1978 gtk_widget_destroy (GTK_WIDGET (self
));
1982 chat_window_update (self
, TRUE
);
1987 chat_window_focus_in_event_cb (GtkWidget
*widget
,
1989 EmpathyChatWindow
*self
)
1991 if (self
->priv
->current_chat
== NULL
) {
1994 empathy_chat_messages_read (self
->priv
->current_chat
);
1996 chat_window_set_urgency_hint (self
, FALSE
);
1998 /* Update the title, since we now mark all unread messages as read. */
1999 chat_window_update_chat_tab_full (self
->priv
->current_chat
, FALSE
);
2005 contacts_loaded_cb (EmpathyIndividualManager
*mgr
,
2006 EmpathyChatWindow
*self
)
2008 chat_window_contact_menu_update (self
);
2012 chat_window_focus_out_event_cb (GtkWidget
*widget
,
2014 EmpathyChatWindow
*self
)
2016 if (self
->priv
->individual_mgr
!= NULL
)
2019 /* Keep the individual manager alive so we won't fetch everything from Folks
2020 * each time we need to use it. Loading FolksAggregator can takes quite a
2021 * while (if user has a huge LDAP abook for example) and it blocks
2022 * the mainloop during most of this loading. We workaround this by loading
2023 * it when the chat window has been unfocused and so, hopefully, not impact
2024 * the reactivity of the chat window too much.
2026 * The individual manager (and so Folks) is needed to know to which
2027 * FolksIndividual a TpContact belongs, including:
2028 * - empathy_chat_get_contact_menu: to list all the personas of the contact
2029 * - empathy_display_individual_info: to invoke gnome-contacts with the
2030 * FolksIndividual.id of the contact
2031 * - drag_data_received_individual_id: to find the individual associated
2032 * with the ID we received from the DnD in order to invite him.
2034 self
->priv
->individual_mgr
= empathy_individual_manager_dup_singleton ();
2036 if (!empathy_individual_manager_get_contacts_loaded (
2037 self
->priv
->individual_mgr
))
2039 /* We want to update the contact menu when Folks is loaded so we can
2040 * list all the personas of the contact. */
2041 tp_g_signal_connect_object (self
->priv
->individual_mgr
, "contacts-loaded",
2042 G_CALLBACK (contacts_loaded_cb
), self
, 0);
2045 g_object_notify (G_OBJECT (self
), "individual-manager");
2051 chat_window_drag_drop (GtkWidget
*widget
,
2052 GdkDragContext
*context
,
2056 EmpathyChatWindow
*self
)
2060 target
= gtk_drag_dest_find_target (widget
, context
, self
->priv
->file_targets
);
2061 if (target
== GDK_NONE
)
2062 target
= gtk_drag_dest_find_target (widget
, context
, self
->priv
->contact_targets
);
2064 if (target
!= GDK_NONE
)
2066 gtk_drag_get_data (widget
, context
, target
, time_
);
2074 chat_window_drag_motion (GtkWidget
*widget
,
2075 GdkDragContext
*context
,
2079 EmpathyChatWindow
*self
)
2083 target
= gtk_drag_dest_find_target (widget
, context
, self
->priv
->file_targets
);
2085 if (target
!= GDK_NONE
)
2087 /* This is a file drag. Ensure the contact is online and set the
2088 drag type to COPY. Note that it's possible that the tab will
2089 be switched by GTK+ after a timeout from drag_motion without
2090 getting another drag_motion to disable the drop. You have
2091 to hold your mouse really still.
2093 EmpathyContact
*contact
;
2095 contact
= empathy_chat_get_remote_contact (self
->priv
->current_chat
);
2097 /* contact is NULL for multi-user chats. We don't do
2098 * file transfers to MUCs. We also don't send files
2099 * to offline contacts or contacts that don't support
2102 if ((contact
== NULL
) || !empathy_contact_is_online (contact
))
2104 gdk_drag_status (context
, 0, time_
);
2108 if (!(empathy_contact_get_capabilities (contact
)
2109 & EMPATHY_CAPABILITIES_FT
))
2111 gdk_drag_status (context
, 0, time_
);
2115 gdk_drag_status (context
, GDK_ACTION_COPY
, time_
);
2119 target
= gtk_drag_dest_find_target (widget
, context
, self
->priv
->contact_targets
);
2120 if (target
!= GDK_NONE
)
2122 /* This is a drag of a contact from a contact list. Set to COPY.
2123 FIXME: If this drag is to a MUC window, it invites the user.
2124 Otherwise, it opens a chat. Should we use a different drag
2125 type for invites? Should we allow ASK?
2127 gdk_drag_status (context
, GDK_ACTION_COPY
, time_
);
2135 drag_data_received_individual_id (EmpathyChatWindow
*self
,
2137 GdkDragContext
*context
,
2140 GtkSelectionData
*selection
,
2145 FolksIndividual
*individual
;
2146 EmpathyTpChat
*chat
;
2147 TpContact
*tp_contact
;
2149 EmpathyContact
*contact
;
2151 id
= (const gchar
*) gtk_selection_data_get_data (selection
);
2153 DEBUG ("DND invididual %s", id
);
2155 if (self
->priv
->current_chat
== NULL
)
2158 chat
= empathy_chat_get_tp_chat (self
->priv
->current_chat
);
2162 if (!empathy_tp_chat_can_add_contact (chat
))
2164 DEBUG ("Can't invite contact to %s",
2165 tp_proxy_get_object_path (chat
));
2169 if (self
->priv
->individual_mgr
== NULL
)
2170 /* Not likely as we have to focus out the chat window in order to start
2171 * the DnD but best to be safe. */
2174 individual
= empathy_individual_manager_lookup_member (
2175 self
->priv
->individual_mgr
, id
);
2176 if (individual
== NULL
)
2178 DEBUG ("Failed to find individual %s", id
);
2182 conn
= tp_channel_get_connection ((TpChannel
*) chat
);
2183 tp_contact
= empathy_get_tp_contact_for_individual (individual
, conn
);
2184 if (tp_contact
== NULL
)
2186 DEBUG ("Can't find a TpContact on connection %s for %s",
2187 tp_proxy_get_object_path (conn
), id
);
2191 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact
),
2192 tp_channel_get_identifier ((TpChannel
*) chat
));
2194 contact
= empathy_contact_dup_from_tp_contact (tp_contact
);
2195 empathy_tp_chat_add (chat
, contact
, NULL
);
2196 g_object_unref (contact
);
2199 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2203 chat_window_drag_data_received (GtkWidget
*widget
,
2204 GdkDragContext
*context
,
2207 GtkSelectionData
*selection
,
2210 EmpathyChatWindow
*self
)
2212 if (info
== DND_DRAG_TYPE_CONTACT_ID
)
2214 EmpathyChat
*chat
= NULL
;
2215 EmpathyChatWindow
*old_window
;
2216 TpAccount
*account
= NULL
;
2217 EmpathyClientFactory
*factory
;
2220 const gchar
*account_id
;
2221 const gchar
*contact_id
;
2223 id
= (const gchar
*) gtk_selection_data_get_data (selection
);
2225 factory
= empathy_client_factory_dup ();
2227 DEBUG ("DND contact from roster with id:'%s'", id
);
2229 strv
= g_strsplit (id
, ":", 2);
2230 if (g_strv_length (strv
) == 2)
2232 account_id
= strv
[0];
2233 contact_id
= strv
[1];
2235 account
= tp_simple_client_factory_ensure_account (
2236 TP_SIMPLE_CLIENT_FACTORY (factory
), account_id
, NULL
, NULL
);
2238 g_object_unref (factory
);
2239 if (account
!= NULL
)
2240 chat
= empathy_chat_window_find_chat (account
, contact_id
, FALSE
);
2243 if (account
== NULL
)
2246 gtk_drag_finish (context
, FALSE
, FALSE
, time_
);
2252 empathy_chat_with_contact_id (account
, contact_id
,
2253 empathy_get_current_action_time (), NULL
, NULL
);
2261 old_window
= chat_window_find_chat (chat
);
2264 if (old_window
== self
)
2266 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2270 empathy_chat_window_move_chat (old_window
, self
, chat
);
2274 empathy_chat_window_add_chat (self
, chat
);
2277 /* Added to take care of any outstanding chat events */
2278 empathy_chat_window_present_chat (chat
,
2279 TP_USER_ACTION_TIME_NOT_USER_ACTION
);
2281 /* We should return TRUE to remove the data when doing
2282 * GDK_ACTION_MOVE, but we don't here otherwise it has
2283 * weird consequences, and we handle that internally
2284 * anyway with add_chat () and remove_chat ().
2286 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2288 else if (info
== DND_DRAG_TYPE_INDIVIDUAL_ID
)
2290 drag_data_received_individual_id (self
, widget
, context
, x
, y
,
2291 selection
, info
, time_
);
2293 else if (info
== DND_DRAG_TYPE_URI_LIST
)
2295 EmpathyContact
*contact
;
2298 contact
= empathy_chat_get_remote_contact (self
->priv
->current_chat
);
2300 /* contact is NULL when current_chat is a multi-user chat.
2301 * We don't do file transfers to MUCs, so just cancel the drag.
2303 if (contact
== NULL
)
2305 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2309 data
= (const gchar
*) gtk_selection_data_get_data (selection
);
2310 empathy_send_file_from_uri_list (contact
, data
);
2312 gtk_drag_finish (context
, TRUE
, FALSE
, time_
);
2314 else if (info
== DND_DRAG_TYPE_TAB
)
2317 EmpathyChatWindow
*old_window
= NULL
;
2321 chat
= (void *) gtk_selection_data_get_data (selection
);
2322 old_window
= chat_window_find_chat (*chat
);
2326 self
->priv
->dnd_same_window
= (old_window
== self
);
2328 DEBUG ("DND tab (within same window: %s)",
2329 self
->priv
->dnd_same_window
? "Yes" : "No");
2334 DEBUG ("DND from unknown source");
2335 gtk_drag_finish (context
, FALSE
, FALSE
, time_
);
2340 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager
*chat_manager
,
2341 guint num_chats_in_manager
,
2342 EmpathyChatWindow
*self
)
2344 gtk_action_set_sensitive (self
->priv
->menu_tabs_undo_close_tab
,
2345 num_chats_in_manager
> 0);
2349 chat_window_finalize (GObject
*object
)
2351 EmpathyChatWindow
*self
= EMPATHY_CHAT_WINDOW (object
);
2353 DEBUG ("Finalized: %p", object
);
2355 g_object_unref (self
->priv
->ui_manager
);
2356 g_object_unref (self
->priv
->chatroom_manager
);
2357 g_object_unref (self
->priv
->notify_mgr
);
2358 g_object_unref (self
->priv
->gsettings_chat
);
2359 g_object_unref (self
->priv
->gsettings_notif
);
2360 g_object_unref (self
->priv
->gsettings_ui
);
2361 g_object_unref (self
->priv
->sound_mgr
);
2362 g_clear_object (&self
->priv
->individual_mgr
);
2364 if (self
->priv
->notification
!= NULL
)
2366 notify_notification_close (self
->priv
->notification
, NULL
);
2367 self
->priv
->notification
= NULL
;
2370 if (self
->priv
->contact_targets
)
2371 gtk_target_list_unref (self
->priv
->contact_targets
);
2373 if (self
->priv
->file_targets
)
2374 gtk_target_list_unref (self
->priv
->file_targets
);
2376 if (self
->priv
->chat_manager
)
2378 g_signal_handler_disconnect (self
->priv
->chat_manager
,
2379 self
->priv
->chat_manager_chats_changed_id
);
2380 g_object_unref (self
->priv
->chat_manager
);
2381 self
->priv
->chat_manager
= NULL
;
2384 chat_windows
= g_list_remove (chat_windows
, self
);
2386 G_OBJECT_CLASS (empathy_chat_window_parent_class
)->finalize (object
);
2390 chat_window_get_property (GObject
*object
,
2395 EmpathyChatWindow
*self
= EMPATHY_CHAT_WINDOW (object
);
2397 switch (property_id
)
2399 case PROP_INDIVIDUAL_MGR
:
2400 g_value_set_object (value
, self
->priv
->individual_mgr
);
2402 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
2408 empathy_chat_window_class_init (EmpathyChatWindowClass
*klass
)
2410 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
2413 object_class
->get_property
= chat_window_get_property
;
2414 object_class
->finalize
= chat_window_finalize
;
2416 spec
= g_param_spec_object ("individual-manager", "individual-manager",
2417 "EmpathyIndividualManager",
2418 EMPATHY_TYPE_INDIVIDUAL_MANAGER
,
2419 G_PARAM_READABLE
| G_PARAM_STATIC_STRINGS
);
2420 g_object_class_install_property (object_class
, PROP_INDIVIDUAL_MGR
, spec
);
2422 g_type_class_add_private (object_class
, sizeof (EmpathyChatWindowPriv
));
2426 chat_window_chat_new_message_cb (GSimpleAction
*action
,
2427 GVariant
*parameter
,
2430 EmpathyChatWindow
*self
= user_data
;
2432 //empathy_new_message_dialog_show (GTK_WINDOW (self));
2437 empathy_chat_window_init (EmpathyChatWindow
*self
)
2440 GtkAccelGroup
*accel_group
;
2445 GtkWidget
*chat_vbox
;
2446 GtkWidget
*main_box
;
2448 EmpathySmileyManager
*smiley_manager
;
2450 self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
2451 EMPATHY_TYPE_CHAT_WINDOW
, EmpathyChatWindowPriv
);
2453 filename
= empathy_file_lookup ("empathy-chat-window.ui", "src");
2454 gui
= tpaw_builder_get_file (filename
,
2455 "chat_vbox", &chat_vbox
,
2456 "main_box", &main_box
,
2457 "ui_manager", &self
->priv
->ui_manager
,
2458 "menu_conv_insert_smiley", &self
->priv
->menu_conv_insert_smiley
,
2459 "menu_conv_favorite", &self
->priv
->menu_conv_favorite
,
2460 "menu_conv_join_chat", &self
->priv
->menu_conv_join_chat
,
2461 "menu_conv_leave_chat", &self
->priv
->menu_conv_leave_chat
,
2462 "menu_conv_always_urgent", &self
->priv
->menu_conv_always_urgent
,
2463 "menu_conv_toggle_contacts", &self
->priv
->menu_conv_toggle_contacts
,
2464 "menu_edit_cut", &self
->priv
->menu_edit_cut
,
2465 "menu_edit_copy", &self
->priv
->menu_edit_copy
,
2466 "menu_edit_paste", &self
->priv
->menu_edit_paste
,
2467 "menu_edit_find", &self
->priv
->menu_edit_find
,
2468 "menu_tabs_next", &self
->priv
->menu_tabs_next
,
2469 "menu_tabs_prev", &self
->priv
->menu_tabs_prev
,
2470 "menu_tabs_undo_close_tab", &self
->priv
->menu_tabs_undo_close_tab
,
2471 "menu_tabs_left", &self
->priv
->menu_tabs_left
,
2472 "menu_tabs_right", &self
->priv
->menu_tabs_right
,
2473 "menu_tabs_detach", &self
->priv
->menu_tabs_detach
,
2477 tpaw_builder_connect (gui
, self
,
2478 "menu_conv", "activate", chat_window_conv_activate_cb
,
2479 "menu_conv_clear", "activate", chat_window_clear_activate_cb
,
2480 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb
,
2481 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb
,
2482 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb
,
2483 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb
,
2484 "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb
,
2485 "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb
,
2486 "menu_conv_close", "activate", chat_window_close_activate_cb
,
2487 "menu_edit", "activate", chat_window_edit_activate_cb
,
2488 "menu_edit_cut", "activate", chat_window_cut_activate_cb
,
2489 "menu_edit_copy", "activate", chat_window_copy_activate_cb
,
2490 "menu_edit_paste", "activate", chat_window_paste_activate_cb
,
2491 "menu_edit_find", "activate", chat_window_find_activate_cb
,
2492 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb
,
2493 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb
,
2494 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb
,
2495 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb
,
2496 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb
,
2497 "menu_tabs_detach", "activate", chat_window_detach_activate_cb
,
2498 "menu_help_contents", "activate", chat_window_help_contents_activate_cb
,
2499 "menu_help_about", "activate", chat_window_help_about_activate_cb
,
2502 empathy_set_css_provider (GTK_WIDGET (self
));
2504 self
->priv
->gsettings_chat
= g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA
);
2505 self
->priv
->gsettings_notif
= g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA
);
2506 self
->priv
->gsettings_ui
= g_settings_new (EMPATHY_PREFS_UI_SCHEMA
);
2507 self
->priv
->chatroom_manager
= empathy_chatroom_manager_dup_singleton (NULL
);
2509 self
->priv
->sound_mgr
= empathy_sound_manager_dup_singleton ();
2511 self
->priv
->notebook
= gtk_notebook_new ();
2512 //gtk_notebook_set_show_tabs (GTK_NOTEBOOK (self->priv->notebook), FALSE);
2514 g_signal_connect (self
->priv
->notebook
, "create-window",
2515 G_CALLBACK (notebook_create_window_cb
), self
);
2517 gtk_container_add (GTK_CONTAINER (self
), main_box
);
2519 gtk_notebook_set_group_name (GTK_NOTEBOOK (self
->priv
->notebook
),
2520 "EmpathyChatWindow");
2521 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self
->priv
->notebook
), TRUE
);
2522 gtk_notebook_popup_enable (GTK_NOTEBOOK (self
->priv
->notebook
));
2523 gtk_box_pack_start (GTK_BOX (chat_vbox
), self
->priv
->notebook
, TRUE
, TRUE
, 0);
2524 gtk_widget_show (self
->priv
->notebook
);
2526 #if 0 /* no top level window yet at this point */
2528 accel_group
= gtk_accel_group_new ();
2529 gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self
))), accel_group
);
2531 for (i
= 0; i
< G_N_ELEMENTS (tab_accel_keys
); i
++)
2533 closure
= g_cclosure_new (G_CALLBACK (chat_window_accel_cb
), self
,
2536 gtk_accel_group_connect (accel_group
, tab_accel_keys
[i
], GDK_MOD1_MASK
, 0,
2540 g_object_unref (accel_group
);
2543 /* Set up drag target lists */
2544 self
->priv
->contact_targets
= gtk_target_list_new (drag_types_dest_contact
,
2545 G_N_ELEMENTS (drag_types_dest_contact
));
2547 self
->priv
->file_targets
= gtk_target_list_new (drag_types_dest_file
,
2548 G_N_ELEMENTS (drag_types_dest_file
));
2550 /* Set up smiley menu */
2551 smiley_manager
= empathy_smiley_manager_dup_singleton ();
2552 submenu
= empathy_smiley_menu_new (smiley_manager
,
2553 chat_window_insert_smiley_activate_cb
, self
);
2555 menu
= gtk_ui_manager_get_widget (self
->priv
->ui_manager
,
2556 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2557 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu
), submenu
);
2558 g_object_unref (smiley_manager
);
2560 /* Set up signals we can't do with ui file since we may need to
2561 * block/unblock them at some later stage.
2564 g_signal_connect (self
, "delete_event",
2565 G_CALLBACK (chat_window_delete_event_cb
), self
);
2566 g_signal_connect (self
, "focus_in_event",
2567 G_CALLBACK (chat_window_focus_in_event_cb
), self
);
2568 g_signal_connect (self
, "focus_out_event",
2569 G_CALLBACK (chat_window_focus_out_event_cb
), self
);
2570 g_signal_connect_after (self
->priv
->notebook
, "switch_page",
2571 G_CALLBACK (chat_window_page_switched_cb
), self
);
2572 g_signal_connect (self
->priv
->notebook
, "page_added",
2573 G_CALLBACK (chat_window_page_added_cb
), self
);
2574 g_signal_connect (self
->priv
->notebook
, "page_removed",
2575 G_CALLBACK (chat_window_page_removed_cb
), self
);
2577 /* Set up drag and drop */
2578 gtk_drag_dest_set (GTK_WIDGET (self
->priv
->notebook
),
2579 GTK_DEST_DEFAULT_HIGHLIGHT
,
2581 G_N_ELEMENTS (drag_types_dest
),
2582 GDK_ACTION_MOVE
| GDK_ACTION_COPY
);
2584 /* connect_after to allow GtkNotebook's built-in tab switching */
2585 g_signal_connect_after (self
->priv
->notebook
, "drag-motion",
2586 G_CALLBACK (chat_window_drag_motion
), self
);
2587 g_signal_connect (self
->priv
->notebook
, "drag-data-received",
2588 G_CALLBACK (chat_window_drag_data_received
), self
);
2589 g_signal_connect (self
->priv
->notebook
, "drag-drop",
2590 G_CALLBACK (chat_window_drag_drop
), self
);
2592 chat_windows
= g_list_prepend (chat_windows
, self
);
2594 /* Set up private details */
2595 self
->priv
->chats
= NULL
;
2596 self
->priv
->current_chat
= NULL
;
2597 self
->priv
->notification
= NULL
;
2599 self
->priv
->notify_mgr
= empathy_notify_manager_dup_singleton ();
2601 self
->priv
->chat_manager
= empathy_chat_manager_dup_singleton ();
2602 self
->priv
->chat_manager_chats_changed_id
= g_signal_connect (
2603 self
->priv
->chat_manager
, "closed-chats-changed",
2604 G_CALLBACK (chat_window_chat_manager_chats_changed_cb
), self
);
2606 chat_window_chat_manager_chats_changed_cb (self
->priv
->chat_manager
,
2607 empathy_chat_manager_get_num_closed_chats (self
->priv
->chat_manager
), self
);
2609 g_object_ref (self
->priv
->ui_manager
);
2610 g_object_unref (gui
);
2613 /* Returns the window to open a new tab in if there is a suitable window,
2614 * otherwise, returns NULL indicating that a new window should be added.
2616 static EmpathyChatWindow
*
2617 empathy_chat_window_get_default (gboolean room
)
2619 GSettings
*gsettings
= g_settings_new (EMPATHY_PREFS_UI_SCHEMA
);
2621 gboolean separate_windows
= TRUE
;
2623 separate_windows
= g_settings_get_boolean (gsettings
,
2624 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS
);
2625 separate_windows
= FALSE
;
2627 g_object_unref (gsettings
);
2629 if (separate_windows
)
2630 /* Always create a new window */
2633 for (l
= chat_windows
; l
; l
= l
->next
)
2635 EmpathyChatWindow
*chat_window
;
2636 guint nb_rooms
, nb_private
;
2638 chat_window
= l
->data
;
2641 empathy_chat_window_get_nb_chats (chat_window
, &nb_rooms
, &nb_private
);
2643 /* Skip the window if there aren't any rooms in it */
2644 if (room
&& nb_rooms
== 0)
2647 /* Skip the window if there aren't any 1-1 chats in it */
2648 if (!room
&& nb_private
== 0)
2659 empathy_chat_window_add_chat (EmpathyChatWindow
*self
,
2663 GtkWidget
*popup_label
;
2665 GValue value
= { 0, };
2667 g_return_if_fail (self
!= NULL
);
2668 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2670 /* Reference the chat object */
2671 g_object_ref (chat
);
2673 /* If this window has just been created, position it */
2674 if (self
->priv
->chats
== NULL
)
2676 const gchar
*name
= "chat-window";
2677 gboolean separate_windows
;
2679 separate_windows
= g_settings_get_boolean (self
->priv
->gsettings_ui
,
2680 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS
);
2681 separate_windows
= FALSE
;
2683 if (empathy_chat_is_room (chat
))
2684 name
= "room-window";
2686 if (separate_windows
)
2690 /* Save current position of the window */
2691 //gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2693 /* First bind to the 'generic' name. So new window for which we didn't
2694 * save a geometry yet will have the geometry of the last saved
2695 * window (bgo #601191). */
2696 //empathy_geometry_bind (GTK_WINDOW (self), name);
2698 /* Restore previous position of the window so the newly created window
2699 * won't be in the same position as the latest saved window and so
2700 * completely hide it. */
2701 //gtk_window_move (GTK_WINDOW (self), x, y);
2703 /* Then bind it to the name of the contact/room so we'll save the
2704 * geometry specific to this window */
2705 name
= empathy_chat_get_id (chat
);
2708 //empathy_geometry_bind (GTK_WINDOW (self), name);
2711 child
= GTK_WIDGET (chat
);
2712 label
= chat_window_create_label (self
, chat
, TRUE
);
2713 popup_label
= chat_window_create_label (self
, chat
, FALSE
);
2714 gtk_widget_show (child
);
2716 g_signal_connect (chat
, "notify::name",
2717 G_CALLBACK (chat_window_chat_notify_cb
), NULL
);
2718 g_signal_connect (chat
, "notify::subject",
2719 G_CALLBACK (chat_window_chat_notify_cb
), NULL
);
2720 g_signal_connect (chat
, "notify::remote-contact",
2721 G_CALLBACK (chat_window_chat_notify_cb
), NULL
);
2722 g_signal_connect (chat
, "notify::sms-channel",
2723 G_CALLBACK (chat_window_chat_notify_cb
), NULL
);
2724 g_signal_connect (chat
, "notify::n-messages-sending",
2725 G_CALLBACK (chat_window_chat_notify_cb
), NULL
);
2726 g_signal_connect (chat
, "notify::nb-unread-messages",
2727 G_CALLBACK (chat_window_chat_notify_cb
), NULL
);
2728 chat_window_chat_notify_cb (chat
);
2730 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self
->priv
->notebook
), child
, label
,
2732 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self
->priv
->notebook
), child
, TRUE
);
2733 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self
->priv
->notebook
), child
, TRUE
);
2734 g_value_init (&value
, G_TYPE_BOOLEAN
);
2735 g_value_set_boolean (&value
, TRUE
);
2736 gtk_container_child_set_property (GTK_CONTAINER (self
->priv
->notebook
),
2737 child
, "tab-expand" , &value
);
2738 gtk_container_child_set_property (GTK_CONTAINER (self
->priv
->notebook
),
2739 child
, "tab-fill" , &value
);
2740 g_value_unset (&value
);
2742 DEBUG ("Chat added (%d references)", G_OBJECT (chat
)->ref_count
);
2746 empathy_chat_window_remove_chat (EmpathyChatWindow
*self
,
2750 EmpathyContact
*remote_contact
;
2751 EmpathyChatManager
*chat_manager
;
2753 g_return_if_fail (self
!= NULL
);
2754 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2756 g_signal_handlers_disconnect_by_func (chat
,
2757 chat_window_chat_notify_cb
, NULL
);
2759 remote_contact
= g_object_get_data (G_OBJECT (chat
),
2760 "chat-window-remote-contact");
2764 g_signal_handlers_disconnect_by_func (remote_contact
,
2765 chat_window_update_chat_tab
, chat
);
2768 chat_manager
= empathy_chat_manager_dup_singleton ();
2769 empathy_chat_manager_closed_chat (chat_manager
, chat
);
2770 g_object_unref (chat_manager
);
2772 position
= gtk_notebook_page_num (GTK_NOTEBOOK (self
->priv
->notebook
),
2774 gtk_notebook_remove_page (GTK_NOTEBOOK (self
->priv
->notebook
), position
);
2776 DEBUG ("Chat removed (%d references)", G_OBJECT (chat
)->ref_count
- 1);
2778 g_object_unref (chat
);
2782 empathy_chat_window_move_chat (EmpathyChatWindow
*old_window
,
2783 EmpathyChatWindow
*new_window
,
2788 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window
));
2789 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window
));
2790 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2792 widget
= GTK_WIDGET (chat
);
2794 DEBUG ("Chat moving with widget:%p (%d references)", widget
,
2795 G_OBJECT (widget
)->ref_count
);
2797 /* We reference here to make sure we don't loose the widget
2798 * and the EmpathyChat object during the move.
2800 g_object_ref (chat
);
2801 g_object_ref (widget
);
2803 empathy_chat_window_remove_chat (old_window
, chat
);
2804 empathy_chat_window_add_chat (new_window
, chat
);
2806 g_object_unref (widget
);
2807 g_object_unref (chat
);
2811 empathy_chat_window_switch_to_chat (EmpathyChatWindow
*self
,
2816 g_return_if_fail (self
!= NULL
);
2817 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2819 page_num
= gtk_notebook_page_num (GTK_NOTEBOOK (self
->priv
->notebook
),
2822 gtk_notebook_set_current_page (GTK_NOTEBOOK (self
->priv
->notebook
),
2827 empathy_chat_window_find_chat (TpAccount
*account
,
2829 gboolean sms_channel
)
2833 g_return_val_if_fail (!TPAW_STR_EMPTY (id
), NULL
);
2835 for (l
= chat_windows
; l
; l
= l
->next
)
2837 EmpathyChatWindow
*window
= l
->data
;
2840 for (ll
= window
->priv
->chats
; ll
; ll
= ll
->next
)
2846 if (account
== empathy_chat_get_account (chat
) &&
2847 !tp_strdiff (id
, empathy_chat_get_id (chat
)) &&
2848 sms_channel
== empathy_chat_is_sms_channel (chat
))
2857 empathy_chat_window_present_chat (EmpathyChat
*chat
,
2860 EmpathyChatWindow
*self
;
2861 guint32 x_timestamp
;
2864 /* initial window */
2865 self
= empathy_chat_window_new ();
2866 gtk_widget_show (GTK_WIDGET (self
));
2870 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
2872 self
= chat_window_find_chat (chat
);
2874 /* If the chat has no window, create one */
2877 self
= empathy_chat_window_get_default (empathy_chat_is_room (chat
));
2880 self
= empathy_chat_window_new ();
2882 /* we want to display the newly created window even if we
2883 * don't present it */
2884 gtk_widget_show (GTK_WIDGET (self
));
2887 empathy_chat_window_add_chat (self
, chat
);
2890 /* Don't force the window to show itself when it wasn't
2891 * an action by the user
2893 if (!tp_user_action_time_should_present (timestamp
, &x_timestamp
))
2896 if (x_timestamp
!= GDK_CURRENT_TIME
)
2898 /* Don't present or switch tab if the action was earlier than the
2899 * last actions X time, accounting for overflow and the first ever
2902 if (self
->priv
->x_user_action_time
!= 0
2903 && X_EARLIER_OR_EQL (x_timestamp
, self
->priv
->x_user_action_time
))
2906 self
->priv
->x_user_action_time
= x_timestamp
;
2909 empathy_chat_window_switch_to_chat (self
, chat
);
2911 /* Don't use tpaw_window_present_with_time () which would move the window
2912 * to our current desktop but move to the window's desktop instead. This is
2913 * more coherent with Shell's 'app is ready' notication which moves the view
2914 * to the app desktop rather than moving the app itself. */
2915 empathy_move_to_window_desktop (GTK_WINDOW (gtk_widget_get_toplevel(GTK_WIDGET(self
))), x_timestamp
);
2917 gtk_widget_grab_focus (chat
->input_text_view
);
2922 empathy_chat_window_get_nb_chats (EmpathyChatWindow
*self
,
2927 guint _nb_rooms
= 0, _nb_private
= 0;
2929 for (l
= self
->priv
->chats
; l
!= NULL
; l
= g_list_next (l
))
2931 if (empathy_chat_is_room (EMPATHY_CHAT (l
->data
)))
2937 if (nb_rooms
!= NULL
)
2938 *nb_rooms
= _nb_rooms
;
2939 if (nb_private
!= NULL
)
2940 *nb_private
= _nb_private
;
2943 EmpathyIndividualManager
*
2944 empathy_chat_window_get_individual_manager (EmpathyChatWindow
*self
)
2946 return self
->priv
->individual_mgr
;