1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2002-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Richard Hult <richard@imendio.com>
23 * Martyn Russell <martyn@imendio.com>
24 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
25 * Xavier Claessens <xclaesse@gmail.com>
33 #include <gdk/gdkkeysyms.h>
34 #include <glib/gi18n-lib.h>
37 #include <telepathy-glib/account-manager.h>
38 #include <telepathy-glib/util.h>
40 #include <libempathy/empathy-log-manager.h>
41 #include <libempathy/empathy-contact-list.h>
42 #include <libempathy/empathy-utils.h>
43 #include <libempathy/empathy-dispatcher.h>
45 #include "empathy-chat.h"
46 #include "empathy-conf.h"
47 #include "empathy-spell.h"
48 #include "empathy-contact-list-store.h"
49 #include "empathy-contact-list-view.h"
50 #include "empathy-contact-menu.h"
51 #include "empathy-theme-manager.h"
52 #include "empathy-smiley-manager.h"
53 #include "empathy-ui-utils.h"
55 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
56 #include <libempathy/empathy-debug.h>
58 #define CHAT_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
59 #define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
60 #define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
61 #define MAX_INPUT_HEIGHT 150
62 #define COMPOSING_STOP_TIMEOUT 5
64 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChat)
66 EmpathyTpChat
*tp_chat
;
71 EmpathyContact
*remote_contact
;
72 gboolean show_contacts
;
74 EmpathyLogManager
*log_manager
;
75 TpAccountManager
*account_manager
;
77 GList
*input_history_current
;
79 GCompletion
*completion
;
80 guint composing_stop_timeout_id
;
81 guint block_events_timeout_id
;
82 TpHandleType handle_type
;
84 gboolean has_input_vscroll
;
89 GtkWidget
*scrolled_window_chat
;
90 GtkWidget
*scrolled_window_input
;
91 GtkWidget
*scrolled_window_contacts
;
92 GtkWidget
*hbox_topic
;
93 GtkWidget
*label_topic
;
94 GtkWidget
*contact_list_view
;
95 GtkWidget
*info_bar_vbox
;
97 guint unread_messages
;
98 /* TRUE if the pending messages can be displayed. This is to avoid to show
99 * pending messages *before* messages from logs. (#603980) */
100 gboolean can_show_pending
;
104 gchar
*text
; /* Original message that was specified
105 * upon entry creation. */
106 gchar
*modified_text
; /* Message that was modified by user.
107 * When no modifications were made, it is NULL */
127 static guint signals
[LAST_SIGNAL
] = { 0 };
129 G_DEFINE_TYPE (EmpathyChat
, empathy_chat
, GTK_TYPE_BIN
);
132 chat_get_property (GObject
*object
,
137 EmpathyChat
*chat
= EMPATHY_CHAT (object
);
138 EmpathyChatPriv
*priv
= GET_PRIV (object
);
142 g_value_set_object (value
, priv
->tp_chat
);
145 g_value_set_object (value
, priv
->account
);
148 g_value_set_string (value
, empathy_chat_get_name (chat
));
151 g_value_set_string (value
, priv
->id
);
154 g_value_set_string (value
, priv
->subject
);
156 case PROP_REMOTE_CONTACT
:
157 g_value_set_object (value
, priv
->remote_contact
);
159 case PROP_SHOW_CONTACTS
:
160 g_value_set_boolean (value
, priv
->show_contacts
);
163 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
169 chat_set_property (GObject
*object
,
174 EmpathyChat
*chat
= EMPATHY_CHAT (object
);
178 empathy_chat_set_tp_chat (chat
, EMPATHY_TP_CHAT (g_value_get_object (value
)));
180 case PROP_SHOW_CONTACTS
:
181 empathy_chat_set_show_contacts (chat
, g_value_get_boolean (value
));
184 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
190 chat_connect_channel_reconnected (EmpathyDispatchOperation
*dispatch
,
194 EmpathyChat
*chat
= EMPATHY_CHAT (user_data
);
195 EmpathyTpChat
*tpchat
;
198 empathy_chat_view_append_event (chat
->view
,
199 _("Failed to reconnect this chat"));
203 tpchat
= EMPATHY_TP_CHAT (
204 empathy_dispatch_operation_get_channel_wrapper (dispatch
));
206 if (empathy_dispatch_operation_claim (dispatch
)) {
207 empathy_chat_set_tp_chat (chat
, tpchat
);
212 reconnected_connection_ready_cb (TpConnection
*connection
,
216 EmpathyChat
*chat
= user_data
;
217 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
220 DEBUG ("connection is not ready: %s", error
->message
);
224 DEBUG ("Account reconnected, request a new Text channel");
226 switch (priv
->handle_type
) {
227 case TP_HANDLE_TYPE_CONTACT
:
228 empathy_dispatcher_chat_with_contact_id (
229 connection
, priv
->id
,
230 chat_connect_channel_reconnected
,
233 case TP_HANDLE_TYPE_ROOM
:
234 empathy_dispatcher_join_muc (connection
,
236 chat_connect_channel_reconnected
,
240 g_assert_not_reached ();
245 g_object_unref (chat
);
249 chat_new_connection_cb (TpAccount
*account
,
253 gchar
*dbus_error_name
,
257 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
258 TpConnection
*connection
;
260 if (new_status
!= TP_CONNECTION_STATUS_CONNECTED
)
263 connection
= tp_account_get_connection (account
);
265 if (priv
->tp_chat
!= NULL
|| account
!= priv
->account
||
266 priv
->handle_type
== TP_HANDLE_TYPE_NONE
||
267 EMP_STR_EMPTY (priv
->id
))
271 tp_connection_call_when_ready (connection
, reconnected_connection_ready_cb
,
276 chat_composing_remove_timeout (EmpathyChat
*chat
)
278 EmpathyChatPriv
*priv
;
280 priv
= GET_PRIV (chat
);
282 if (priv
->composing_stop_timeout_id
) {
283 g_source_remove (priv
->composing_stop_timeout_id
);
284 priv
->composing_stop_timeout_id
= 0;
289 chat_composing_stop_timeout_cb (EmpathyChat
*chat
)
291 EmpathyChatPriv
*priv
;
293 priv
= GET_PRIV (chat
);
295 priv
->composing_stop_timeout_id
= 0;
296 empathy_tp_chat_set_state (priv
->tp_chat
,
297 TP_CHANNEL_CHAT_STATE_PAUSED
);
303 chat_composing_start (EmpathyChat
*chat
)
305 EmpathyChatPriv
*priv
;
307 priv
= GET_PRIV (chat
);
309 if (priv
->composing_stop_timeout_id
) {
310 /* Just restart the timeout */
311 chat_composing_remove_timeout (chat
);
313 empathy_tp_chat_set_state (priv
->tp_chat
,
314 TP_CHANNEL_CHAT_STATE_COMPOSING
);
317 priv
->composing_stop_timeout_id
= g_timeout_add_seconds (
318 COMPOSING_STOP_TIMEOUT
,
319 (GSourceFunc
) chat_composing_stop_timeout_cb
,
324 chat_composing_stop (EmpathyChat
*chat
)
326 EmpathyChatPriv
*priv
;
328 priv
= GET_PRIV (chat
);
330 chat_composing_remove_timeout (chat
);
331 empathy_tp_chat_set_state (priv
->tp_chat
,
332 TP_CHANNEL_CHAT_STATE_ACTIVE
);
336 chat_input_history_entry_cmp (InputHistoryEntry
*entry
,
339 if (!tp_strdiff (entry
->text
, text
)) {
340 if (entry
->modified_text
!= NULL
) {
341 /* Modified entry and single string cannot be equal. */
349 static InputHistoryEntry
*
350 chat_input_history_entry_new_with_text (const gchar
*text
)
352 InputHistoryEntry
*entry
;
353 entry
= g_slice_new0 (InputHistoryEntry
);
354 entry
->text
= g_strdup (text
);
360 chat_input_history_entry_free (InputHistoryEntry
*entry
)
362 g_free (entry
->text
);
363 g_free (entry
->modified_text
);
364 g_slice_free (InputHistoryEntry
, entry
);
368 chat_input_history_entry_revert (InputHistoryEntry
*entry
)
370 g_free (entry
->modified_text
);
371 entry
->modified_text
= NULL
;
375 chat_input_history_entry_update_text (InputHistoryEntry
*entry
,
380 if (!tp_strdiff (text
, entry
->text
)) {
381 g_free (entry
->modified_text
);
382 entry
->modified_text
= NULL
;
386 old
= entry
->modified_text
;
387 entry
->modified_text
= g_strdup (text
);
392 chat_input_history_entry_get_text (InputHistoryEntry
*entry
)
398 if (entry
->modified_text
!= NULL
) {
399 return entry
->modified_text
;
405 chat_input_history_remove_item (GList
*list
,
408 list
= g_list_remove_link (list
, item
);
409 chat_input_history_entry_free (item
->data
);
410 g_list_free_1 (item
);
415 chat_input_history_revert (EmpathyChat
*chat
)
417 EmpathyChatPriv
*priv
;
421 InputHistoryEntry
*entry
;
423 priv
= GET_PRIV (chat
);
424 list
= priv
->input_history
;
427 DEBUG ("No input history");
431 /* Delete temporary entry */
432 if (priv
->input_history_current
!= NULL
) {
434 list
= chat_input_history_remove_item (list
, item1
);
435 if (priv
->input_history_current
== item1
) {
436 /* Removed temporary entry was current entry */
437 priv
->input_history
= list
;
438 priv
->input_history_current
= NULL
;
443 /* There is no entry to revert */
447 /* Restore the current history entry to original value */
448 item1
= priv
->input_history_current
;
450 chat_input_history_entry_revert (entry
);
452 /* Remove restored entry if there is other occurance before this entry */
453 item2
= g_list_find_custom (list
, chat_input_history_entry_get_text (entry
),
454 (GCompareFunc
) chat_input_history_entry_cmp
);
455 if (item2
!= item1
) {
456 list
= chat_input_history_remove_item (list
, item1
);
459 /* Remove other occurance of the restored entry */
460 item2
= g_list_find_custom (item1
->next
,
461 chat_input_history_entry_get_text (entry
),
462 (GCompareFunc
) chat_input_history_entry_cmp
);
464 list
= chat_input_history_remove_item (list
, item2
);
468 priv
->input_history_current
= NULL
;
469 priv
->input_history
= list
;
473 chat_input_history_add (EmpathyChat
*chat
,
477 EmpathyChatPriv
*priv
;
480 InputHistoryEntry
*entry
;
482 priv
= GET_PRIV (chat
);
484 list
= priv
->input_history
;
486 /* Remove any other occurances of this entry, if not temporary */
488 while ((item
= g_list_find_custom (list
, str
,
489 (GCompareFunc
) chat_input_history_entry_cmp
)) != NULL
) {
490 list
= chat_input_history_remove_item (list
, item
);
493 /* Trim the list to the last 10 items */
494 while (g_list_length (list
) > 10) {
495 item
= g_list_last (list
);
497 list
= chat_input_history_remove_item (list
, item
);
505 entry
= chat_input_history_entry_new_with_text (str
);
506 list
= g_list_prepend (list
, entry
);
508 /* Set the list and the current item pointer */
509 priv
->input_history
= list
;
511 priv
->input_history_current
= list
;
514 priv
->input_history_current
= NULL
;
519 chat_input_history_get_next (EmpathyChat
*chat
)
521 EmpathyChatPriv
*priv
;
525 priv
= GET_PRIV (chat
);
527 if (priv
->input_history
== NULL
) {
528 DEBUG ("No input history, next entry is NULL");
531 g_assert (priv
->input_history_current
!= NULL
);
533 if ((item
= g_list_next (priv
->input_history_current
)) == NULL
)
535 item
= priv
->input_history_current
;
538 msg
= chat_input_history_entry_get_text (item
->data
);
540 DEBUG ("Returning next entry: '%s'", msg
);
542 priv
->input_history_current
= item
;
548 chat_input_history_get_prev (EmpathyChat
*chat
)
550 EmpathyChatPriv
*priv
;
554 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
556 priv
= GET_PRIV (chat
);
558 if (priv
->input_history
== NULL
) {
559 DEBUG ("No input history, previous entry is NULL");
563 if (priv
->input_history_current
== NULL
)
567 else if ((item
= g_list_previous (priv
->input_history_current
)) == NULL
)
569 item
= priv
->input_history_current
;
572 msg
= chat_input_history_entry_get_text (item
->data
);
574 DEBUG ("Returning previous entry: '%s'", msg
);
576 priv
->input_history_current
= item
;
582 chat_input_history_update (EmpathyChat
*chat
,
583 GtkTextBuffer
*buffer
)
585 EmpathyChatPriv
*priv
;
586 GtkTextIter start
, end
;
588 InputHistoryEntry
*entry
;
590 priv
= GET_PRIV (chat
);
592 gtk_text_buffer_get_bounds (buffer
, &start
, &end
);
593 text
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
595 if (priv
->input_history_current
== NULL
) {
596 /* Add the current text temporarily to the history */
597 chat_input_history_add (chat
, text
, TRUE
);
602 /* Save the changes in the history */
603 entry
= priv
->input_history_current
->data
;
604 if (tp_strdiff (chat_input_history_entry_get_text (entry
), text
)) {
605 chat_input_history_entry_update_text (entry
, text
);
612 chat_command_join_cb (EmpathyDispatchOperation
*dispatch
,
616 EmpathyChat
*chat
= user_data
;
619 DEBUG ("Error: %s", error
->message
);
620 empathy_chat_view_append_event (chat
->view
,
621 _("Failed to join chatroom"));
628 } ChatCommandMsgData
;
631 chat_command_msg_cb (EmpathyDispatchOperation
*dispatch
,
635 ChatCommandMsgData
*data
= user_data
;
638 empathy_chat_view_append_event (data
->chat
->view
,
639 _("Failed to open private chat"));
643 if (!EMP_STR_EMPTY (data
->message
)) {
644 EmpathyTpChat
*tpchat
;
645 EmpathyMessage
*message
;
647 tpchat
= EMPATHY_TP_CHAT (
648 empathy_dispatch_operation_get_channel_wrapper (dispatch
));
650 message
= empathy_message_new (data
->message
);
651 empathy_tp_chat_send (tpchat
, message
);
652 g_object_unref (message
);
656 g_free (data
->message
);
657 g_slice_free (ChatCommandMsgData
, data
);
661 chat_command_clear (EmpathyChat
*chat
,
664 empathy_chat_view_clear (chat
->view
);
668 chat_command_topic (EmpathyChat
*chat
,
671 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
672 EmpathyTpChatProperty
*property
;
673 GValue value
= {0, };
675 property
= empathy_tp_chat_get_property (priv
->tp_chat
, "subject");
676 if (property
== NULL
) {
677 empathy_chat_view_append_event (chat
->view
,
678 _("Topic not supported on this conversation"));
682 if (!(property
->flags
& TP_PROPERTY_FLAG_WRITE
)) {
683 empathy_chat_view_append_event (chat
->view
,
684 _("You are not allowed to change the topic"));
688 g_value_init (&value
, G_TYPE_STRING
);
689 g_value_set_string (&value
, strv
[1]);
690 empathy_tp_chat_set_property (priv
->tp_chat
, "subject", &value
);
691 g_value_unset (&value
);
695 chat_command_join (EmpathyChat
*chat
,
698 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
699 TpConnection
*connection
;
701 connection
= empathy_tp_chat_get_connection (priv
->tp_chat
);
702 empathy_dispatcher_join_muc (connection
, strv
[1],
703 chat_command_join_cb
,
708 chat_command_msg_internal (EmpathyChat
*chat
,
709 const gchar
*contact_id
,
710 const gchar
*message
)
712 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
713 TpConnection
*connection
;
714 ChatCommandMsgData
*data
;
716 /* FIXME: We should probably search in members alias. But this
717 * is enough for IRC */
718 data
= g_slice_new (ChatCommandMsgData
);
720 data
->message
= g_strdup (message
);
721 connection
= empathy_tp_chat_get_connection (priv
->tp_chat
);
722 empathy_dispatcher_chat_with_contact_id (connection
, contact_id
,
728 chat_command_query (EmpathyChat
*chat
,
731 /* If <message> part is not defined,
732 * strv[2] will be the terminal NULL */
733 chat_command_msg_internal (chat
, strv
[1], strv
[2]);
737 chat_command_msg (EmpathyChat
*chat
,
740 chat_command_msg_internal (chat
, strv
[1], strv
[2]);
744 chat_command_nick (EmpathyChat
*chat
,
747 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
748 TpConnection
*connection
;
749 GHashTable
*new_alias
;
752 connection
= tp_account_get_connection (priv
->account
);
753 handle
= tp_connection_get_self_handle (connection
);
754 new_alias
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
755 g_hash_table_insert (new_alias
, GUINT_TO_POINTER (handle
), strv
[1]);
757 tp_cli_connection_interface_aliasing_call_set_aliases (connection
, -1,
758 new_alias
, NULL
, NULL
, NULL
, NULL
);
760 g_hash_table_destroy (new_alias
);
764 chat_command_me (EmpathyChat
*chat
,
767 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
768 EmpathyMessage
*message
;
770 message
= empathy_message_new (strv
[1]);
771 empathy_message_set_tptype (message
, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION
);
772 empathy_tp_chat_send (priv
->tp_chat
, message
);
773 g_object_unref (message
);
777 chat_command_say (EmpathyChat
*chat
,
780 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
781 EmpathyMessage
*message
;
783 message
= empathy_message_new (strv
[1]);
784 empathy_tp_chat_send (priv
->tp_chat
, message
);
785 g_object_unref (message
);
788 static void chat_command_help (EmpathyChat
*chat
, GStrv strv
);
790 typedef void (*ChatCommandFunc
) (EmpathyChat
*chat
, GStrv strv
);
796 ChatCommandFunc func
;
800 static ChatCommandItem commands
[] = {
801 {"clear", 1, 1, chat_command_clear
,
802 N_("/clear, clear all messages from the current conversation")},
804 {"topic", 2, 2, chat_command_topic
,
805 N_("/topic <topic>, set the topic of the current conversation")},
807 {"join", 2, 2, chat_command_join
,
808 N_("/join <chatroom id>, join a new chatroom")},
810 {"j", 2, 2, chat_command_join
,
811 N_("/j <chatroom id>, join a new chatroom")},
813 {"query", 2, 3, chat_command_query
,
814 N_("/query <contact id> [<message>], open a private chat")},
816 {"msg", 3, 3, chat_command_msg
,
817 N_("/msg <contact id> <message>, open a private chat")},
819 {"nick", 2, 2, chat_command_nick
,
820 N_("/nick <nickname>, change your nickname on current server")},
822 {"me", 2, 2, chat_command_me
,
823 N_("/me <message>, send an ACTION message to the current conversation")},
825 {"say", 2, 2, chat_command_say
,
826 N_("/say <message>, send <message> to the current conversation. "
827 "This is used to send a message starting with a '/'. For example: "
828 "\"/say /join is used to join a new chatroom\"")},
830 {"help", 1, 2, chat_command_help
,
831 N_("/help [<command>], show all supported commands. "
832 "If <command> is defined, show its usage.")},
836 chat_command_show_help (EmpathyChat
*chat
,
837 ChatCommandItem
*item
)
841 str
= g_strdup_printf (_("Usage: %s"), _(item
->help
));
842 empathy_chat_view_append_event (chat
->view
, str
);
847 chat_command_help (EmpathyChat
*chat
,
852 /* If <command> part is not defined,
853 * strv[1] will be the terminal NULL */
854 if (strv
[1] == NULL
) {
855 for (i
= 0; i
< G_N_ELEMENTS (commands
); i
++) {
856 empathy_chat_view_append_event (chat
->view
,
857 _(commands
[i
].help
));
862 for (i
= 0; i
< G_N_ELEMENTS (commands
); i
++) {
863 if (g_ascii_strcasecmp (strv
[1], commands
[i
].prefix
) == 0) {
864 chat_command_show_help (chat
, &commands
[i
]);
869 empathy_chat_view_append_event (chat
->view
,
870 _("Unknown command"));
874 chat_command_parse (const gchar
*text
, guint max_parts
)
879 DEBUG ("Parse command, parts=%d text=\"%s\":", max_parts
, text
);
881 array
= g_ptr_array_sized_new (max_parts
+ 1);
882 while (max_parts
> 1) {
885 /* Skip white spaces */
886 while (g_ascii_isspace (*text
)) {
890 /* Search the end of this part, until first space. */
891 for (end
= text
; *end
!= '\0' && !g_ascii_isspace (*end
); end
++)
897 item
= g_strndup (text
, end
- text
);
898 g_ptr_array_add (array
, item
);
899 DEBUG ("\tITEM: \"%s\"", item
);
905 /* Append last part if not empty */
906 item
= g_strstrip (g_strdup (text
));
907 if (!EMP_STR_EMPTY (item
)) {
908 g_ptr_array_add (array
, item
);
909 DEBUG ("\tITEM: \"%s\"", item
);
914 /* Make the array NULL-terminated */
915 g_ptr_array_add (array
, NULL
);
917 return (GStrv
) g_ptr_array_free (array
, FALSE
);
921 has_prefix_case (const gchar
*s
,
924 return g_ascii_strncasecmp (s
, prefix
, strlen (prefix
)) == 0;
928 chat_send (EmpathyChat
*chat
,
931 EmpathyChatPriv
*priv
;
932 EmpathyMessage
*message
;
935 if (EMP_STR_EMPTY (msg
)) {
939 priv
= GET_PRIV (chat
);
941 chat_input_history_add (chat
, msg
, FALSE
);
944 gboolean second_slash
= FALSE
;
945 const gchar
*iter
= msg
+ 1;
947 for (i
= 0; i
< G_N_ELEMENTS (commands
); i
++) {
952 if (!has_prefix_case (msg
+ 1, commands
[i
].prefix
)) {
955 c
= *(msg
+ 1 + strlen (commands
[i
].prefix
));
956 if (c
!= '\0' && !g_ascii_isspace (c
)) {
960 /* We can't use g_strsplit here because it does
961 * not deal correctly if we have more than one space
963 strv
= chat_command_parse (msg
+ 1, commands
[i
].max_parts
);
965 strv_len
= g_strv_length (strv
);
966 if (strv_len
< commands
[i
].min_parts
||
967 strv_len
> commands
[i
].max_parts
) {
968 chat_command_show_help (chat
, &commands
[i
]);
973 commands
[i
].func (chat
, strv
);
978 /* Also allow messages with two slashes before the
979 * first space, so it is possible to send a /unix/path.
980 * This heuristic is kind of crap. */
981 while (*iter
!= '\0' && !g_ascii_isspace (*iter
)) {
990 empathy_chat_view_append_event (chat
->view
,
991 _("Unknown command, see /help for the available"
997 message
= empathy_message_new (msg
);
998 empathy_tp_chat_send (priv
->tp_chat
, message
);
999 g_object_unref (message
);
1003 chat_input_text_view_send (EmpathyChat
*chat
)
1005 EmpathyChatPriv
*priv
;
1006 GtkTextBuffer
*buffer
;
1007 GtkTextIter start
, end
;
1010 priv
= GET_PRIV (chat
);
1012 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1014 gtk_text_buffer_get_bounds (buffer
, &start
, &end
);
1015 msg
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
1017 /* clear the input field */
1018 gtk_text_buffer_set_text (buffer
, "", -1);
1019 /* delete input history modifications */
1020 chat_input_history_revert (chat
);
1022 chat_send (chat
, msg
);
1027 chat_state_changed_cb (EmpathyTpChat
*tp_chat
,
1028 EmpathyContact
*contact
,
1029 TpChannelChatState state
,
1032 EmpathyChatPriv
*priv
;
1034 gboolean was_composing
;
1036 priv
= GET_PRIV (chat
);
1038 if (empathy_contact_is_user (contact
)) {
1039 /* We don't care about our own chat state */
1043 was_composing
= (priv
->compositors
!= NULL
);
1045 /* Find the contact in the list. After that l is the list elem or NULL */
1046 for (l
= priv
->compositors
; l
; l
= l
->next
) {
1047 if (contact
== l
->data
) {
1053 case TP_CHANNEL_CHAT_STATE_GONE
:
1054 case TP_CHANNEL_CHAT_STATE_INACTIVE
:
1055 case TP_CHANNEL_CHAT_STATE_PAUSED
:
1056 case TP_CHANNEL_CHAT_STATE_ACTIVE
:
1057 /* Contact is not composing */
1059 priv
->compositors
= g_list_remove_link (priv
->compositors
, l
);
1060 g_object_unref (l
->data
);
1064 case TP_CHANNEL_CHAT_STATE_COMPOSING
:
1065 /* Contact is composing */
1067 priv
->compositors
= g_list_prepend (priv
->compositors
,
1068 g_object_ref (contact
));
1072 g_assert_not_reached ();
1075 DEBUG ("Was composing: %s now composing: %s",
1076 was_composing
? "yes" : "no",
1077 priv
->compositors
? "yes" : "no");
1079 if ((was_composing
&& !priv
->compositors
) ||
1080 (!was_composing
&& priv
->compositors
)) {
1081 /* Composing state changed */
1082 g_signal_emit (chat
, signals
[COMPOSING
], 0,
1083 priv
->compositors
!= NULL
);
1088 chat_message_received (EmpathyChat
*chat
, EmpathyMessage
*message
)
1090 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1091 EmpathyContact
*sender
;
1093 sender
= empathy_message_get_sender (message
);
1095 DEBUG ("Appending new message from %s (%d)",
1096 empathy_contact_get_name (sender
),
1097 empathy_contact_get_handle (sender
));
1099 empathy_chat_view_append_message (chat
->view
, message
);
1101 /* We received a message so the contact is no longer composing */
1102 chat_state_changed_cb (priv
->tp_chat
, sender
,
1103 TP_CHANNEL_CHAT_STATE_ACTIVE
,
1106 priv
->unread_messages
++;
1107 g_signal_emit (chat
, signals
[NEW_MESSAGE
], 0, message
);
1111 chat_message_received_cb (EmpathyTpChat
*tp_chat
,
1112 EmpathyMessage
*message
,
1115 chat_message_received (chat
, message
);
1116 empathy_tp_chat_acknowledge_message (tp_chat
, message
);
1120 chat_send_error_cb (EmpathyTpChat
*tp_chat
,
1121 const gchar
*message_body
,
1122 TpChannelTextSendError error_code
,
1128 switch (error_code
) {
1129 case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE
:
1130 error
= _("offline");
1132 case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT
:
1133 error
= _("invalid contact");
1135 case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED
:
1136 error
= _("permission denied");
1138 case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG
:
1139 error
= _("too long message");
1141 case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED
:
1142 error
= _("not implemented");
1145 error
= _("unknown");
1149 str
= g_strdup_printf (_("Error sending message '%s': %s"),
1152 empathy_chat_view_append_event (chat
->view
, str
);
1157 chat_property_changed_cb (EmpathyTpChat
*tp_chat
,
1162 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1164 if (!tp_strdiff (name
, "subject")) {
1165 g_free (priv
->subject
);
1166 priv
->subject
= g_value_dup_string (value
);
1167 g_object_notify (G_OBJECT (chat
), "subject");
1169 if (EMP_STR_EMPTY (priv
->subject
)) {
1170 gtk_widget_hide (priv
->hbox_topic
);
1172 gtk_label_set_text (GTK_LABEL (priv
->label_topic
), priv
->subject
);
1173 gtk_widget_show (priv
->hbox_topic
);
1175 if (priv
->block_events_timeout_id
== 0) {
1178 if (!EMP_STR_EMPTY (priv
->subject
)) {
1179 str
= g_strdup_printf (_("Topic set to: %s"), priv
->subject
);
1181 str
= g_strdup (_("No topic defined"));
1183 empathy_chat_view_append_event (EMPATHY_CHAT (chat
)->view
, str
);
1187 else if (!tp_strdiff (name
, "name")) {
1188 g_free (priv
->name
);
1189 priv
->name
= g_value_dup_string (value
);
1190 g_object_notify (G_OBJECT (chat
), "name");
1195 chat_input_text_buffer_changed_cb (GtkTextBuffer
*buffer
,
1198 EmpathyChatPriv
*priv
;
1199 GtkTextIter start
, end
;
1201 gboolean spell_checker
= FALSE
;
1203 priv
= GET_PRIV (chat
);
1205 if (gtk_text_buffer_get_char_count (buffer
) == 0) {
1206 chat_composing_stop (chat
);
1208 chat_composing_start (chat
);
1211 empathy_conf_get_bool (empathy_conf_get (),
1212 EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED
,
1215 gtk_text_buffer_get_start_iter (buffer
, &start
);
1217 if (!spell_checker
) {
1218 gtk_text_buffer_get_end_iter (buffer
, &end
);
1219 gtk_text_buffer_remove_tag_by_name (buffer
, "misspelled", &start
, &end
);
1223 if (!empathy_spell_supported ()) {
1227 /* NOTE: this is really inefficient, we shouldn't have to
1228 reiterate the whole buffer each time and check each work
1231 gboolean correct
= FALSE
;
1234 if (gtk_text_iter_is_start (&start
)) {
1237 if (!gtk_text_iter_forward_word_end (&end
)) {
1238 /* no whole word yet */
1242 if (!gtk_text_iter_forward_word_end (&end
)) {
1243 /* must be the end of the buffer */
1248 gtk_text_iter_backward_word_start (&start
);
1251 str
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
1253 /* spell check string if not a command */
1254 if (str
[0] != '/') {
1255 correct
= empathy_spell_check (str
);
1261 gtk_text_buffer_apply_tag_by_name (buffer
, "misspelled", &start
, &end
);
1263 gtk_text_buffer_remove_tag_by_name (buffer
, "misspelled", &start
, &end
);
1268 /* set start iter to the end iters position */
1274 chat_input_key_press_event_cb (GtkWidget
*widget
,
1278 EmpathyChatPriv
*priv
;
1281 GtkWidget
*text_view_sw
;
1283 priv
= GET_PRIV (chat
);
1285 /* Catch ctrl+up/down so we can traverse messages we sent */
1286 if ((event
->state
& GDK_CONTROL_MASK
) &&
1287 (event
->keyval
== GDK_Up
||
1288 event
->keyval
== GDK_Down
)) {
1289 GtkTextBuffer
*buffer
;
1292 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1293 chat_input_history_update (chat
, buffer
);
1295 if (event
->keyval
== GDK_Up
) {
1296 str
= chat_input_history_get_next (chat
);
1298 str
= chat_input_history_get_prev (chat
);
1301 g_signal_handlers_block_by_func (buffer
,
1302 chat_input_text_buffer_changed_cb
,
1304 gtk_text_buffer_set_text (buffer
, str
? str
: "", -1);
1305 g_signal_handlers_unblock_by_func (buffer
,
1306 chat_input_text_buffer_changed_cb
,
1312 /* Catch enter but not ctrl/shift-enter */
1313 if (IS_ENTER (event
->keyval
) &&
1314 !(event
->state
& (GDK_SHIFT_MASK
| GDK_CONTROL_MASK
))) {
1317 /* This is to make sure that kinput2 gets the enter. And if
1318 * it's handled there we shouldn't send on it. This is because
1319 * kinput2 uses Enter to commit letters. See:
1320 * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
1323 view
= GTK_TEXT_VIEW (chat
->input_text_view
);
1324 if (gtk_im_context_filter_keypress (view
->im_context
, event
)) {
1325 GTK_TEXT_VIEW (chat
->input_text_view
)->need_im_reset
= TRUE
;
1329 chat_input_text_view_send (chat
);
1333 text_view_sw
= gtk_widget_get_parent (GTK_WIDGET (chat
->view
));
1335 if (IS_ENTER (event
->keyval
) &&
1336 (event
->state
& (GDK_SHIFT_MASK
| GDK_CONTROL_MASK
))) {
1337 /* Newline for shift/control-enter. */
1340 if (!(event
->state
& GDK_CONTROL_MASK
) &&
1341 event
->keyval
== GDK_Page_Up
) {
1342 adj
= gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw
));
1343 gtk_adjustment_set_value (adj
, gtk_adjustment_get_value (adj
) - gtk_adjustment_get_page_size (adj
));
1346 if ((event
->state
& GDK_CONTROL_MASK
) != GDK_CONTROL_MASK
&&
1347 event
->keyval
== GDK_Page_Down
) {
1348 adj
= gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw
));
1349 val
= MIN (gtk_adjustment_get_value (adj
) + gtk_adjustment_get_page_size (adj
),
1350 gtk_adjustment_get_upper (adj
) - gtk_adjustment_get_page_size (adj
));
1351 gtk_adjustment_set_value (adj
, val
);
1354 if (!(event
->state
& (GDK_CONTROL_MASK
| GDK_SHIFT_MASK
)) &&
1355 event
->keyval
== GDK_Tab
) {
1356 GtkTextBuffer
*buffer
;
1357 GtkTextIter start
, current
;
1358 gchar
*nick
, *completed
;
1359 GList
*list
, *completed_list
;
1360 gboolean is_start_of_buffer
;
1362 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (EMPATHY_CHAT (chat
)->input_text_view
));
1363 gtk_text_buffer_get_iter_at_mark (buffer
, ¤t
, gtk_text_buffer_get_insert (buffer
));
1365 /* Get the start of the nick to complete. */
1366 gtk_text_buffer_get_iter_at_mark (buffer
, &start
, gtk_text_buffer_get_insert (buffer
));
1367 gtk_text_iter_backward_word_start (&start
);
1368 is_start_of_buffer
= gtk_text_iter_is_start (&start
);
1370 list
= empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (priv
->tp_chat
));
1371 g_completion_add_items (priv
->completion
, list
);
1373 nick
= gtk_text_buffer_get_text (buffer
, &start
, ¤t
, FALSE
);
1374 completed_list
= g_completion_complete (priv
->completion
,
1383 gchar
*complete_char
= NULL
;
1385 gtk_text_buffer_delete (buffer
, &start
, ¤t
);
1387 len
= g_list_length (completed_list
);
1390 /* If we only have one hit, use that text
1391 * instead of the text in completed since the
1392 * completed text will use the typed string
1393 * which might be cased all wrong.
1396 text
= empathy_contact_get_name (completed_list
->data
);
1401 gtk_text_buffer_insert_at_cursor (buffer
, text
, strlen (text
));
1403 if (len
== 1 && is_start_of_buffer
&&
1404 empathy_conf_get_string (empathy_conf_get (),
1405 EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR
,
1407 complete_char
!= NULL
) {
1408 gtk_text_buffer_insert_at_cursor (buffer
,
1410 strlen (complete_char
));
1411 gtk_text_buffer_insert_at_cursor (buffer
, " ", 1);
1412 g_free (complete_char
);
1418 g_completion_clear_items (priv
->completion
);
1420 g_list_foreach (list
, (GFunc
) g_object_unref
, NULL
);
1430 chat_text_view_focus_in_event_cb (GtkWidget
*widget
,
1434 gtk_widget_grab_focus (chat
->input_text_view
);
1440 chat_input_set_size_request_idle (gpointer sw
)
1442 gtk_widget_set_size_request (sw
, -1, MAX_INPUT_HEIGHT
);
1448 chat_input_size_request_cb (GtkWidget
*widget
,
1449 GtkRequisition
*requisition
,
1452 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1455 sw
= gtk_widget_get_parent (widget
);
1456 if (requisition
->height
>= MAX_INPUT_HEIGHT
&& !priv
->has_input_vscroll
) {
1457 g_idle_add (chat_input_set_size_request_idle
, sw
);
1458 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
1461 priv
->has_input_vscroll
= TRUE
;
1464 if (requisition
->height
< MAX_INPUT_HEIGHT
&& priv
->has_input_vscroll
) {
1465 gtk_widget_set_size_request (sw
, -1, -1);
1466 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
1469 priv
->has_input_vscroll
= FALSE
;
1474 chat_input_realize_cb (GtkWidget
*widget
,
1477 DEBUG ("Setting focus to the input text view");
1478 if (gtk_widget_is_sensitive (widget
)) {
1479 gtk_widget_grab_focus (widget
);
1484 chat_insert_smiley_activate_cb (EmpathySmileyManager
*manager
,
1485 EmpathySmiley
*smiley
,
1488 EmpathyChat
*chat
= EMPATHY_CHAT (user_data
);
1489 GtkTextBuffer
*buffer
;
1492 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1494 gtk_text_buffer_get_end_iter (buffer
, &iter
);
1495 gtk_text_buffer_insert (buffer
, &iter
, smiley
->str
, -1);
1497 gtk_text_buffer_get_end_iter (buffer
, &iter
);
1498 gtk_text_buffer_insert (buffer
, &iter
, " ", -1);
1509 static EmpathyChatSpell
*
1510 chat_spell_new (EmpathyChat
*chat
,
1515 EmpathyChatSpell
*chat_spell
;
1517 chat_spell
= g_slice_new0 (EmpathyChatSpell
);
1519 chat_spell
->chat
= g_object_ref (chat
);
1520 chat_spell
->word
= g_strdup (word
);
1521 chat_spell
->start
= start
;
1522 chat_spell
->end
= end
;
1528 chat_spell_free (EmpathyChatSpell
*chat_spell
)
1530 g_object_unref (chat_spell
->chat
);
1531 g_free (chat_spell
->word
);
1532 g_slice_free (EmpathyChatSpell
, chat_spell
);
1536 chat_spelling_menu_activate_cb (GtkMenuItem
*menu_item
,
1537 EmpathyChatSpell
*chat_spell
)
1539 empathy_chat_correct_word (chat_spell
->chat
,
1540 &(chat_spell
->start
),
1542 gtk_menu_item_get_label (menu_item
));
1546 chat_spelling_build_menu (EmpathyChatSpell
*chat_spell
)
1548 GtkWidget
*menu
, *menu_item
;
1549 GList
*suggestions
, *l
;
1551 menu
= gtk_menu_new ();
1552 suggestions
= empathy_spell_get_suggestions (chat_spell
->word
);
1553 if (suggestions
== NULL
) {
1554 menu_item
= gtk_menu_item_new_with_label (_("(No Suggestions)"));
1555 gtk_widget_set_sensitive (menu_item
, FALSE
);
1556 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), menu_item
);
1558 for (l
= suggestions
; l
; l
= l
->next
) {
1559 menu_item
= gtk_menu_item_new_with_label (l
->data
);
1560 g_signal_connect (G_OBJECT (menu_item
),
1562 G_CALLBACK (chat_spelling_menu_activate_cb
),
1564 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), menu_item
);
1567 empathy_spell_free_suggestions (suggestions
);
1569 gtk_widget_show_all (menu
);
1575 chat_text_send_cb (GtkMenuItem
*menuitem
,
1578 chat_input_text_view_send (chat
);
1582 chat_input_populate_popup_cb (GtkTextView
*view
,
1586 EmpathyChatPriv
*priv
;
1587 GtkTextBuffer
*buffer
;
1588 GtkTextTagTable
*table
;
1591 GtkTextIter iter
, start
, end
;
1594 EmpathyChatSpell
*chat_spell
;
1595 GtkWidget
*spell_menu
;
1596 EmpathySmileyManager
*smiley_manager
;
1597 GtkWidget
*smiley_menu
;
1600 priv
= GET_PRIV (chat
);
1601 buffer
= gtk_text_view_get_buffer (view
);
1603 /* Add the emoticon menu. */
1604 item
= gtk_separator_menu_item_new ();
1605 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1606 gtk_widget_show (item
);
1608 item
= gtk_image_menu_item_new_with_mnemonic (_("Insert Smiley"));
1609 image
= gtk_image_new_from_icon_name ("face-smile",
1610 GTK_ICON_SIZE_MENU
);
1611 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item
), image
);
1612 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1613 gtk_widget_show (item
);
1615 smiley_manager
= empathy_smiley_manager_dup_singleton ();
1616 smiley_menu
= empathy_smiley_menu_new (smiley_manager
,
1617 chat_insert_smiley_activate_cb
,
1619 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item
), smiley_menu
);
1620 g_object_unref (smiley_manager
);
1622 /* Add the Send menu item. */
1623 gtk_text_buffer_get_bounds (buffer
, &start
, &end
);
1624 str
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
1625 if (!EMP_STR_EMPTY (str
)) {
1626 item
= gtk_menu_item_new_with_mnemonic (_("_Send"));
1627 g_signal_connect (G_OBJECT (item
), "activate",
1628 G_CALLBACK (chat_text_send_cb
), chat
);
1629 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1630 gtk_widget_show (item
);
1634 /* Add the spell check menu item. */
1635 table
= gtk_text_buffer_get_tag_table (buffer
);
1636 tag
= gtk_text_tag_table_lookup (table
, "misspelled");
1637 gtk_widget_get_pointer (GTK_WIDGET (view
), &x
, &y
);
1638 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view
),
1639 GTK_TEXT_WINDOW_WIDGET
,
1642 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view
), &iter
, x
, y
);
1644 if (gtk_text_iter_backward_to_tag_toggle (&start
, tag
) &&
1645 gtk_text_iter_forward_to_tag_toggle (&end
, tag
)) {
1647 str
= gtk_text_buffer_get_text (buffer
,
1648 &start
, &end
, FALSE
);
1650 if (!EMP_STR_EMPTY (str
)) {
1651 chat_spell
= chat_spell_new (chat
, str
, start
, end
);
1652 g_object_set_data_full (G_OBJECT (menu
),
1653 "chat_spell", chat_spell
,
1654 (GDestroyNotify
) chat_spell_free
);
1656 item
= gtk_separator_menu_item_new ();
1657 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1658 gtk_widget_show (item
);
1660 item
= gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
1661 image
= gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK
,
1662 GTK_ICON_SIZE_MENU
);
1663 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item
), image
);
1665 spell_menu
= chat_spelling_build_menu (chat_spell
);
1666 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item
), spell_menu
);
1668 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1669 gtk_widget_show (item
);
1674 chat_log_filter (EmpathyMessage
*message
,
1677 EmpathyChat
*chat
= (EmpathyChat
*) user_data
;
1678 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1679 const GList
*pending
;
1681 pending
= empathy_tp_chat_get_pending_messages (priv
->tp_chat
);
1683 for (; pending
; pending
= g_list_next (pending
)) {
1684 if (empathy_message_equal (message
, pending
->data
)) {
1693 chat_add_logs (EmpathyChat
*chat
)
1695 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1696 gboolean is_chatroom
;
1697 GList
*messages
, *l
;
1703 /* Turn off scrolling temporarily */
1704 empathy_chat_view_scroll (chat
->view
, FALSE
);
1706 /* Add messages from last conversation */
1707 is_chatroom
= priv
->handle_type
== TP_HANDLE_TYPE_ROOM
;
1709 messages
= empathy_log_manager_get_filtered_messages (priv
->log_manager
,
1717 for (l
= messages
; l
; l
= g_list_next (l
)) {
1718 empathy_chat_view_append_message (chat
->view
, l
->data
);
1719 g_object_unref (l
->data
);
1722 g_list_free (messages
);
1724 /* Turn back on scrolling */
1725 empathy_chat_view_scroll (chat
->view
, TRUE
);
1729 chat_contacts_completion_func (const gchar
*s1
,
1733 gchar
*tmp
, *nick1
, *nick2
;
1740 return s1
? -1 : +1;
1743 tmp
= g_utf8_normalize (s1
, -1, G_NORMALIZE_DEFAULT
);
1744 nick1
= g_utf8_casefold (tmp
, -1);
1747 tmp
= g_utf8_normalize (s2
, -1, G_NORMALIZE_DEFAULT
);
1748 nick2
= g_utf8_casefold (tmp
, -1);
1751 ret
= strncmp (nick1
, nick2
, n
);
1760 build_part_message (guint reason
,
1762 EmpathyContact
*actor
,
1763 const gchar
*message
)
1765 GString
*s
= g_string_new ("");
1766 const gchar
*actor_name
= NULL
;
1768 if (actor
!= NULL
) {
1769 actor_name
= empathy_contact_get_name (actor
);
1772 /* Having an actor only really makes sense for a few actions... */
1774 case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE
:
1775 g_string_append_printf (s
, _("%s has disconnected"), name
);
1777 case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED
:
1778 if (actor_name
!= NULL
) {
1779 /* translators: reverse the order of these arguments
1780 * if the kicked should come before the kicker in your locale.
1782 g_string_append_printf (s
, _("%1$s was kicked by %2$s"),
1785 g_string_append_printf (s
, _("%s was kicked"), name
);
1788 case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED
:
1789 if (actor_name
!= NULL
) {
1790 /* translators: reverse the order of these arguments
1791 * if the banned should come before the banner in your locale.
1793 g_string_append_printf (s
, _("%1$s was banned by %2$s"),
1796 g_string_append_printf (s
, _("%s was banned"), name
);
1800 g_string_append_printf (s
, _("%s has left the room"), name
);
1803 if (!EMP_STR_EMPTY (message
)) {
1804 /* Note to translators: this string is appended to
1805 * notifications like "foo has left the room", with the message
1806 * given by the user living the room. If this poses a problem,
1807 * please let us know. :-)
1809 g_string_append_printf (s
, _(" (%s)"), message
);
1812 return g_string_free (s
, FALSE
);
1816 chat_members_changed_cb (EmpathyTpChat
*tp_chat
,
1817 EmpathyContact
*contact
,
1818 EmpathyContact
*actor
,
1824 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1825 const gchar
*name
= empathy_contact_get_name (contact
);
1828 g_return_if_fail (TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED
!= reason
);
1830 if (priv
->block_events_timeout_id
!= 0)
1834 str
= g_strdup_printf (_("%s has joined the room"),
1837 str
= build_part_message (reason
, name
, actor
, message
);
1840 empathy_chat_view_append_event (chat
->view
, str
);
1845 chat_member_renamed_cb (EmpathyTpChat
*tp_chat
,
1846 EmpathyContact
*old_contact
,
1847 EmpathyContact
*new_contact
,
1852 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1854 g_return_if_fail (TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED
== reason
);
1856 if (priv
->block_events_timeout_id
== 0) {
1859 str
= g_strdup_printf (_("%s is now known as %s"),
1860 empathy_contact_get_name (old_contact
),
1861 empathy_contact_get_name (new_contact
));
1862 empathy_chat_view_append_event (chat
->view
, str
);
1869 chat_reset_size_request (gpointer widget
)
1871 gtk_widget_set_size_request (widget
, -1, -1);
1877 chat_update_contacts_visibility (EmpathyChat
*chat
,
1880 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1881 GtkAllocation allocation
;
1883 if (!priv
->scrolled_window_contacts
) {
1887 if (priv
->remote_contact
!= NULL
) {
1891 if (show
&& priv
->contact_list_view
== NULL
) {
1892 EmpathyContactListStore
*store
;
1895 /* We are adding the contact list to the chat, we don't want the
1896 * chat view to become too small. If the chat view is already
1897 * smaller than 250 make sure that size won't change. If the
1898 * chat view is bigger the contact list will take some space on
1899 * it but we make sure the chat view don't become smaller than
1900 * 250. Relax the size request once the resize is done */
1901 gtk_widget_get_allocation (priv
->vbox_left
, &allocation
);
1902 min_width
= MIN (allocation
.width
, 250);
1903 gtk_widget_set_size_request (priv
->vbox_left
, min_width
, -1);
1904 g_idle_add (chat_reset_size_request
, priv
->vbox_left
);
1906 if (priv
->contacts_width
> 0) {
1907 gtk_paned_set_position (GTK_PANED (priv
->hpaned
),
1908 priv
->contacts_width
);
1911 store
= empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv
->tp_chat
));
1912 priv
->contact_list_view
= GTK_WIDGET (empathy_contact_list_view_new (store
,
1913 EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP
,
1914 EMPATHY_CONTACT_FEATURE_CHAT
|
1915 EMPATHY_CONTACT_FEATURE_CALL
|
1916 EMPATHY_CONTACT_FEATURE_LOG
|
1917 EMPATHY_CONTACT_FEATURE_INFO
));
1918 gtk_container_add (GTK_CONTAINER (priv
->scrolled_window_contacts
),
1919 priv
->contact_list_view
);
1920 gtk_widget_show (priv
->contact_list_view
);
1921 gtk_widget_show (priv
->scrolled_window_contacts
);
1922 g_object_unref (store
);
1924 priv
->contacts_width
= gtk_paned_get_position (GTK_PANED (priv
->hpaned
));
1925 gtk_widget_hide (priv
->scrolled_window_contacts
);
1926 if (priv
->contact_list_view
!= NULL
) {
1927 gtk_widget_destroy (priv
->contact_list_view
);
1928 priv
->contact_list_view
= NULL
;
1934 empathy_chat_set_show_contacts (EmpathyChat
*chat
,
1937 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1939 priv
->show_contacts
= show
;
1941 chat_update_contacts_visibility (chat
, show
);
1943 g_object_notify (G_OBJECT (chat
), "show-contacts");
1947 chat_remote_contact_changed_cb (EmpathyChat
*chat
)
1949 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1951 if (priv
->remote_contact
!= NULL
) {
1952 g_object_unref (priv
->remote_contact
);
1953 priv
->remote_contact
= NULL
;
1958 priv
->id
= g_strdup (empathy_tp_chat_get_id (priv
->tp_chat
));
1959 priv
->remote_contact
= empathy_tp_chat_get_remote_contact (priv
->tp_chat
);
1960 if (priv
->remote_contact
!= NULL
) {
1961 g_object_ref (priv
->remote_contact
);
1962 priv
->handle_type
= TP_HANDLE_TYPE_CONTACT
;
1964 else if (priv
->tp_chat
!= NULL
) {
1967 channel
= empathy_tp_chat_get_channel (priv
->tp_chat
);
1968 g_object_get (channel
, "handle-type", &priv
->handle_type
, NULL
);
1971 chat_update_contacts_visibility (chat
, priv
->show_contacts
);
1973 g_object_notify (G_OBJECT (chat
), "remote-contact");
1974 g_object_notify (G_OBJECT (chat
), "id");
1978 chat_destroy_cb (EmpathyTpChat
*tp_chat
,
1981 EmpathyChatPriv
*priv
;
1983 priv
= GET_PRIV (chat
);
1985 if (!priv
->tp_chat
) {
1989 chat_composing_remove_timeout (chat
);
1990 g_object_unref (priv
->tp_chat
);
1991 priv
->tp_chat
= NULL
;
1992 g_object_notify (G_OBJECT (chat
), "tp-chat");
1994 empathy_chat_view_append_event (chat
->view
, _("Disconnected"));
1995 gtk_widget_set_sensitive (chat
->input_text_view
, FALSE
);
1997 chat_update_contacts_visibility (chat
, FALSE
);
2001 chat_hpaned_pos_changed_cb (GtkWidget
* hpaned
, gpointer user_data
)
2004 hpaned_pos
= gtk_paned_get_position (GTK_PANED(hpaned
));
2005 empathy_conf_set_int (empathy_conf_get (),
2006 EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS
,
2013 show_pending_messages (EmpathyChat
*chat
) {
2014 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2015 const GList
*messages
, *l
;
2017 if (chat
->view
== NULL
|| priv
->tp_chat
== NULL
)
2020 if (!priv
->can_show_pending
)
2023 messages
= empathy_tp_chat_get_pending_messages (priv
->tp_chat
);
2025 for (l
= messages
; l
!= NULL
; l
= g_list_next (l
)) {
2026 EmpathyMessage
*message
= EMPATHY_MESSAGE (l
->data
);
2027 chat_message_received (chat
, message
);
2029 empathy_tp_chat_acknowledge_messages (priv
->tp_chat
, messages
);
2033 chat_create_ui (EmpathyChat
*chat
)
2035 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2039 GtkTextBuffer
*buffer
;
2042 filename
= empathy_file_lookup ("empathy-chat.ui",
2044 gui
= empathy_builder_get_file (filename
,
2045 "chat_widget", &priv
->widget
,
2046 "hpaned", &priv
->hpaned
,
2047 "vbox_left", &priv
->vbox_left
,
2048 "scrolled_window_chat", &priv
->scrolled_window_chat
,
2049 "scrolled_window_input", &priv
->scrolled_window_input
,
2050 "hbox_topic", &priv
->hbox_topic
,
2051 "label_topic", &priv
->label_topic
,
2052 "scrolled_window_contacts", &priv
->scrolled_window_contacts
,
2053 "info_bar_vbox", &priv
->info_bar_vbox
,
2057 /* Add message view. */
2058 chat
->view
= empathy_theme_manager_create_view (empathy_theme_manager_get ());
2059 /* If this is a GtkTextView, it's set as a drag destination for text/plain
2060 and other types, even though it's non-editable and doesn't accept any
2061 drags. This steals drag motion for anything inside the scrollbars,
2062 making drag destinations on chat windows far less useful.
2064 gtk_drag_dest_unset (GTK_WIDGET (chat
->view
));
2065 g_signal_connect (chat
->view
, "focus_in_event",
2066 G_CALLBACK (chat_text_view_focus_in_event_cb
),
2068 gtk_container_add (GTK_CONTAINER (priv
->scrolled_window_chat
),
2069 GTK_WIDGET (chat
->view
));
2070 gtk_widget_show (GTK_WIDGET (chat
->view
));
2072 /* Add input GtkTextView */
2073 chat
->input_text_view
= g_object_new (GTK_TYPE_TEXT_VIEW
,
2074 "pixels-above-lines", 2,
2075 "pixels-below-lines", 2,
2076 "pixels-inside-wrap", 1,
2079 "wrap-mode", GTK_WRAP_WORD_CHAR
,
2081 g_signal_connect (chat
->input_text_view
, "key-press-event",
2082 G_CALLBACK (chat_input_key_press_event_cb
),
2084 g_signal_connect (chat
->input_text_view
, "size-request",
2085 G_CALLBACK (chat_input_size_request_cb
),
2087 g_signal_connect (chat
->input_text_view
, "realize",
2088 G_CALLBACK (chat_input_realize_cb
),
2090 g_signal_connect (chat
->input_text_view
, "populate-popup",
2091 G_CALLBACK (chat_input_populate_popup_cb
),
2093 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
2094 g_signal_connect (buffer
, "changed",
2095 G_CALLBACK (chat_input_text_buffer_changed_cb
),
2097 gtk_text_buffer_create_tag (buffer
, "misspelled",
2098 "underline", PANGO_UNDERLINE_ERROR
,
2100 gtk_container_add (GTK_CONTAINER (priv
->scrolled_window_input
),
2101 chat
->input_text_view
);
2102 gtk_widget_show (chat
->input_text_view
);
2104 /* Initialy hide the topic, will be shown if not empty */
2105 gtk_widget_hide (priv
->hbox_topic
);
2107 g_signal_connect (priv
->hpaned
, "notify::position",
2108 G_CALLBACK (chat_hpaned_pos_changed_cb
),
2111 /* Load the paned position */
2112 if (empathy_conf_get_int (empathy_conf_get (),
2113 EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS
,
2116 gtk_paned_set_position (GTK_PANED(priv
->hpaned
), paned_pos
);
2118 /* Set widget focus order */
2119 list
= g_list_append (NULL
, priv
->scrolled_window_input
);
2120 gtk_container_set_focus_chain (GTK_CONTAINER (priv
->vbox_left
), list
);
2123 list
= g_list_append (NULL
, priv
->vbox_left
);
2124 list
= g_list_append (list
, priv
->scrolled_window_contacts
);
2125 gtk_container_set_focus_chain (GTK_CONTAINER (priv
->hpaned
), list
);
2128 list
= g_list_append (NULL
, priv
->hpaned
);
2129 list
= g_list_append (list
, priv
->hbox_topic
);
2130 gtk_container_set_focus_chain (GTK_CONTAINER (priv
->widget
), list
);
2133 /* Add the main widget in the chat widget */
2134 gtk_container_add (GTK_CONTAINER (chat
), priv
->widget
);
2135 g_object_unref (gui
);
2139 chat_size_request (GtkWidget
*widget
,
2140 GtkRequisition
*requisition
)
2142 GtkBin
*bin
= GTK_BIN (widget
);
2145 requisition
->width
= gtk_container_get_border_width (GTK_CONTAINER (widget
)) * 2;
2146 requisition
->height
= gtk_container_get_border_width (GTK_CONTAINER (widget
)) * 2;
2148 child
= gtk_bin_get_child (bin
);
2150 if (child
&& gtk_widget_get_visible (child
))
2152 GtkRequisition child_requisition
;
2154 gtk_widget_size_request (child
, &child_requisition
);
2156 requisition
->width
+= child_requisition
.width
;
2157 requisition
->height
+= child_requisition
.height
;
2162 chat_size_allocate (GtkWidget
*widget
,
2163 GtkAllocation
*allocation
)
2165 GtkBin
*bin
= GTK_BIN (widget
);
2166 GtkAllocation child_allocation
;
2169 gtk_widget_set_allocation (widget
, allocation
);
2171 child
= gtk_bin_get_child (bin
);
2173 if (child
&& gtk_widget_get_visible (child
))
2175 child_allocation
.x
= allocation
->x
+ gtk_container_get_border_width (GTK_CONTAINER (widget
));
2176 child_allocation
.y
= allocation
->y
+ gtk_container_get_border_width (GTK_CONTAINER (widget
));
2177 child_allocation
.width
= MAX (allocation
->width
- gtk_container_get_border_width (GTK_CONTAINER (widget
)) * 2, 0);
2178 child_allocation
.height
= MAX (allocation
->height
- gtk_container_get_border_width (GTK_CONTAINER (widget
)) * 2, 0);
2180 gtk_widget_size_allocate (child
, &child_allocation
);
2185 chat_finalize (GObject
*object
)
2188 EmpathyChatPriv
*priv
;
2190 chat
= EMPATHY_CHAT (object
);
2191 priv
= GET_PRIV (chat
);
2193 DEBUG ("Finalized: %p", object
);
2195 g_list_foreach (priv
->input_history
, (GFunc
) chat_input_history_entry_free
, NULL
);
2196 g_list_free (priv
->input_history
);
2198 g_list_foreach (priv
->compositors
, (GFunc
) g_object_unref
, NULL
);
2199 g_list_free (priv
->compositors
);
2201 chat_composing_remove_timeout (chat
);
2203 g_object_unref (priv
->account_manager
);
2204 g_object_unref (priv
->log_manager
);
2206 if (priv
->tp_chat
) {
2207 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
2208 chat_destroy_cb
, chat
);
2209 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
2210 chat_message_received_cb
, chat
);
2211 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
2212 chat_send_error_cb
, chat
);
2213 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
2214 chat_state_changed_cb
, chat
);
2215 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
2216 chat_property_changed_cb
, chat
);
2217 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
2218 chat_members_changed_cb
, chat
);
2219 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
2220 chat_remote_contact_changed_cb
, chat
);
2221 empathy_tp_chat_close (priv
->tp_chat
);
2222 g_object_unref (priv
->tp_chat
);
2224 if (priv
->account
) {
2225 g_object_unref (priv
->account
);
2227 if (priv
->remote_contact
) {
2228 g_object_unref (priv
->remote_contact
);
2231 if (priv
->block_events_timeout_id
) {
2232 g_source_remove (priv
->block_events_timeout_id
);
2236 g_free (priv
->name
);
2237 g_free (priv
->subject
);
2238 g_completion_free (priv
->completion
);
2240 G_OBJECT_CLASS (empathy_chat_parent_class
)->finalize (object
);
2244 chat_constructed (GObject
*object
)
2246 EmpathyChat
*chat
= EMPATHY_CHAT (object
);
2247 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2249 if (priv
->handle_type
!= TP_HANDLE_TYPE_ROOM
)
2250 chat_add_logs (chat
);
2251 priv
->can_show_pending
= TRUE
;
2252 show_pending_messages (chat
);
2256 empathy_chat_class_init (EmpathyChatClass
*klass
)
2258 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS (klass
);
2259 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
2261 object_class
->finalize
= chat_finalize
;
2262 object_class
->get_property
= chat_get_property
;
2263 object_class
->set_property
= chat_set_property
;
2264 object_class
->constructed
= chat_constructed
;
2266 widget_class
->size_request
= chat_size_request
;
2267 widget_class
->size_allocate
= chat_size_allocate
;
2269 g_object_class_install_property (object_class
,
2271 g_param_spec_object ("tp-chat",
2273 "The tp chat object",
2274 EMPATHY_TYPE_TP_CHAT
,
2277 G_PARAM_STATIC_STRINGS
));
2278 g_object_class_install_property (object_class
,
2280 g_param_spec_object ("account",
2281 "Account of the chat",
2282 "The account of the chat",
2285 G_PARAM_STATIC_STRINGS
));
2286 g_object_class_install_property (object_class
,
2288 g_param_spec_string ("id",
2290 "The id of the chat",
2293 G_PARAM_STATIC_STRINGS
));
2294 g_object_class_install_property (object_class
,
2296 g_param_spec_string ("name",
2298 "The name of the chat",
2301 G_PARAM_STATIC_STRINGS
));
2302 g_object_class_install_property (object_class
,
2304 g_param_spec_string ("subject",
2306 "The subject or topic of the chat",
2309 G_PARAM_STATIC_STRINGS
));
2310 g_object_class_install_property (object_class
,
2311 PROP_REMOTE_CONTACT
,
2312 g_param_spec_object ("remote-contact",
2313 "The remote contact",
2314 "The remote contact is any",
2315 EMPATHY_TYPE_CONTACT
,
2317 G_PARAM_STATIC_STRINGS
));
2318 g_object_class_install_property (object_class
,
2320 g_param_spec_boolean ("show-contacts",
2321 "Contacts' visibility",
2322 "The visibility of the contacts' list",
2325 G_PARAM_STATIC_STRINGS
));
2327 signals
[COMPOSING
] =
2328 g_signal_new ("composing",
2329 G_OBJECT_CLASS_TYPE (object_class
),
2333 g_cclosure_marshal_VOID__BOOLEAN
,
2337 signals
[NEW_MESSAGE
] =
2338 g_signal_new ("new-message",
2339 G_OBJECT_CLASS_TYPE (object_class
),
2343 g_cclosure_marshal_VOID__OBJECT
,
2345 1, EMPATHY_TYPE_MESSAGE
);
2347 g_type_class_add_private (object_class
, sizeof (EmpathyChatPriv
));
2351 chat_block_events_timeout_cb (gpointer data
)
2353 EmpathyChatPriv
*priv
= GET_PRIV (data
);
2355 priv
->block_events_timeout_id
= 0;
2361 account_manager_prepared_cb (GObject
*source_object
,
2362 GAsyncResult
*result
,
2365 GList
*accounts
, *l
;
2366 TpAccountManager
*account_manager
= TP_ACCOUNT_MANAGER (source_object
);
2367 EmpathyChat
*chat
= user_data
;
2368 GError
*error
= NULL
;
2370 if (!tp_account_manager_prepare_finish (account_manager
, result
, &error
)) {
2371 DEBUG ("Failed to prepare the account manager: %s", error
->message
);
2372 g_error_free (error
);
2376 accounts
= tp_account_manager_get_valid_accounts (account_manager
);
2378 for (l
= accounts
; l
!= NULL
; l
= l
->next
) {
2379 TpAccount
*account
= l
->data
;
2380 empathy_signal_connect_weak (account
, "status-changed",
2381 G_CALLBACK (chat_new_connection_cb
),
2385 g_list_free (accounts
);
2389 empathy_chat_init (EmpathyChat
*chat
)
2391 EmpathyChatPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (chat
,
2392 EMPATHY_TYPE_CHAT
, EmpathyChatPriv
);
2395 priv
->log_manager
= empathy_log_manager_dup_singleton ();
2396 priv
->contacts_width
= -1;
2397 priv
->input_history
= NULL
;
2398 priv
->input_history_current
= NULL
;
2399 priv
->account_manager
= tp_account_manager_dup ();
2401 tp_account_manager_prepare_async (priv
->account_manager
, NULL
,
2402 account_manager_prepared_cb
, chat
);
2404 empathy_conf_get_bool (empathy_conf_get (),
2405 EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS
,
2406 &priv
->show_contacts
);
2408 /* Block events for some time to avoid having "has come online" or
2409 * "joined" messages. */
2410 priv
->block_events_timeout_id
=
2411 g_timeout_add_seconds (1, chat_block_events_timeout_cb
, chat
);
2413 /* Add nick name completion */
2414 priv
->completion
= g_completion_new ((GCompletionFunc
) empathy_contact_get_name
);
2415 g_completion_set_compare (priv
->completion
, chat_contacts_completion_func
);
2417 chat_create_ui (chat
);
2421 empathy_chat_new (EmpathyTpChat
*tp_chat
)
2423 return g_object_new (EMPATHY_TYPE_CHAT
, "tp-chat", tp_chat
, NULL
);
2427 empathy_chat_get_tp_chat (EmpathyChat
*chat
)
2429 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2431 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
2433 return priv
->tp_chat
;
2436 static void display_password_info_bar (EmpathyChat
*self
,
2440 provide_password_cb (GObject
*tp_chat
,
2444 EmpathyChat
*self
= EMPATHY_CHAT (user_data
);
2445 EmpathyChatPriv
*priv
= GET_PRIV (self
);
2446 GError
*error
= NULL
;
2448 if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat
), res
,
2450 DEBUG ("error: %s", error
->message
);
2451 /* FIXME: what should we do if that's another error? Close the channel?
2452 * Display the raw D-Bus error to the user isn't very useful */
2453 if (g_error_matches (error
, TP_ERRORS
, TP_ERROR_AUTHENTICATION_FAILED
))
2454 display_password_info_bar (self
, TRUE
);
2455 g_error_free (error
);
2460 gtk_widget_set_sensitive (priv
->hpaned
, TRUE
);
2461 gtk_widget_grab_focus (self
->input_text_view
);
2465 password_infobar_response_cb (GtkWidget
*info_bar
,
2469 EmpathyChatPriv
*priv
= GET_PRIV (self
);
2471 const gchar
*password
;
2473 if (response_id
!= GTK_RESPONSE_OK
)
2476 entry
= g_object_get_data (G_OBJECT (info_bar
), "password-entry");
2477 g_assert (entry
!= NULL
);
2479 password
= gtk_entry_get_text (GTK_ENTRY (entry
));
2481 empathy_tp_chat_provide_password_async (priv
->tp_chat
, password
,
2482 provide_password_cb
, self
);
2485 gtk_widget_destroy (info_bar
);
2489 password_entry_activate_cb (GtkWidget
*entry
,
2490 GtkWidget
*info_bar
)
2492 gtk_info_bar_response (GTK_INFO_BAR (info_bar
), GTK_RESPONSE_OK
);
2496 passwd_join_button_cb (GtkButton
*button
,
2497 GtkWidget
*info_bar
)
2499 gtk_info_bar_response (GTK_INFO_BAR (info_bar
), GTK_RESPONSE_OK
);
2503 display_password_info_bar (EmpathyChat
*self
,
2506 EmpathyChatPriv
*priv
= GET_PRIV (self
);
2507 GtkWidget
*info_bar
;
2508 GtkWidget
*content_area
;
2515 GtkMessageType type
;
2516 const gchar
*msg
, *button_label
;
2519 /* Previous password was wrong */
2520 type
= GTK_MESSAGE_ERROR
;
2521 msg
= _("Wrong password; please try again:");
2522 button_label
= _("Retry");
2525 /* First time we're trying to join */
2526 type
= GTK_MESSAGE_QUESTION
;
2527 msg
= _("This room is protected by a password:");
2528 button_label
= _("Join");
2531 info_bar
= gtk_info_bar_new ();
2532 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar
), type
);
2534 content_area
= gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar
));
2536 hbox
= gtk_hbox_new (FALSE
, 3);
2537 gtk_container_add (GTK_CONTAINER (content_area
), hbox
);
2540 image
= gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION
,
2541 GTK_ICON_SIZE_DIALOG
);
2542 gtk_box_pack_start (GTK_BOX (hbox
), image
, FALSE
, FALSE
, 0);
2545 label
= gtk_label_new (msg
);
2546 gtk_box_pack_start (GTK_BOX (hbox
), label
, FALSE
, FALSE
, 0);
2548 /* Add password entry */
2549 entry
= gtk_entry_new ();
2550 gtk_entry_set_visibility (GTK_ENTRY (entry
), FALSE
);
2551 gtk_box_pack_start (GTK_BOX (hbox
), entry
, TRUE
, TRUE
, 0);
2553 g_signal_connect (entry
, "activate",
2554 G_CALLBACK (password_entry_activate_cb
), info_bar
);
2556 /* Focus the password entry once it's realized */
2557 g_signal_connect (entry
, "realize", G_CALLBACK (gtk_widget_grab_focus
), NULL
);
2559 /* Add 'Join' button */
2560 alig
= gtk_alignment_new (0, 0.5, 0, 0);
2562 button
= gtk_button_new_with_label (button_label
);
2563 gtk_container_add (GTK_CONTAINER (alig
), button
);
2564 gtk_box_pack_start (GTK_BOX (hbox
), alig
, FALSE
, FALSE
, 0);
2566 g_signal_connect (button
, "clicked", G_CALLBACK (passwd_join_button_cb
),
2569 g_object_set_data (G_OBJECT (info_bar
), "password-entry", entry
);
2571 gtk_box_pack_start (GTK_BOX (priv
->info_bar_vbox
), info_bar
,
2573 gtk_widget_show_all (hbox
);
2575 g_signal_connect (info_bar
, "response",
2576 G_CALLBACK (password_infobar_response_cb
), self
);
2578 gtk_widget_show_all (info_bar
);
2582 chat_password_needed_changed_cb (EmpathyChat
*self
)
2584 EmpathyChatPriv
*priv
= GET_PRIV (self
);
2586 if (empathy_tp_chat_password_needed (priv
->tp_chat
)) {
2587 display_password_info_bar (self
, FALSE
);
2588 gtk_widget_set_sensitive (priv
->hpaned
, FALSE
);
2593 empathy_chat_set_tp_chat (EmpathyChat
*chat
,
2594 EmpathyTpChat
*tp_chat
)
2596 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2597 TpConnection
*connection
;
2598 GPtrArray
*properties
;
2600 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2601 g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat
));
2602 g_return_if_fail (empathy_tp_chat_is_ready (tp_chat
));
2604 if (priv
->tp_chat
) {
2608 if (priv
->account
) {
2609 g_object_unref (priv
->account
);
2612 priv
->tp_chat
= g_object_ref (tp_chat
);
2613 connection
= empathy_tp_chat_get_connection (priv
->tp_chat
);
2614 priv
->account
= g_object_ref (empathy_get_account_for_connection (connection
));
2616 g_signal_connect (tp_chat
, "destroy",
2617 G_CALLBACK (chat_destroy_cb
),
2619 g_signal_connect (tp_chat
, "message-received",
2620 G_CALLBACK (chat_message_received_cb
),
2622 g_signal_connect (tp_chat
, "send-error",
2623 G_CALLBACK (chat_send_error_cb
),
2625 g_signal_connect (tp_chat
, "chat-state-changed",
2626 G_CALLBACK (chat_state_changed_cb
),
2628 g_signal_connect (tp_chat
, "property-changed",
2629 G_CALLBACK (chat_property_changed_cb
),
2631 g_signal_connect (tp_chat
, "members-changed",
2632 G_CALLBACK (chat_members_changed_cb
),
2634 g_signal_connect (tp_chat
, "member-renamed",
2635 G_CALLBACK (chat_member_renamed_cb
),
2637 g_signal_connect_swapped (tp_chat
, "notify::remote-contact",
2638 G_CALLBACK (chat_remote_contact_changed_cb
),
2640 g_signal_connect_swapped (tp_chat
, "notify::password-needed",
2641 G_CALLBACK (chat_password_needed_changed_cb
),
2644 /* Get initial value of properties */
2645 properties
= empathy_tp_chat_get_properties (priv
->tp_chat
);
2646 if (properties
!= NULL
) {
2649 for (i
= 0; i
< properties
->len
; i
++) {
2650 EmpathyTpChatProperty
*property
;
2652 property
= g_ptr_array_index (properties
, i
);
2653 if (property
->value
== NULL
)
2656 chat_property_changed_cb (priv
->tp_chat
,
2663 chat_remote_contact_changed_cb (chat
);
2665 if (chat
->input_text_view
) {
2666 gtk_widget_set_sensitive (chat
->input_text_view
, TRUE
);
2667 if (priv
->block_events_timeout_id
== 0) {
2668 empathy_chat_view_append_event (chat
->view
, _("Connected"));
2672 g_object_notify (G_OBJECT (chat
), "tp-chat");
2673 g_object_notify (G_OBJECT (chat
), "id");
2674 g_object_notify (G_OBJECT (chat
), "account");
2676 /* This is a noop when tp-chat is set at object construction time and causes
2677 * the pending messages to be show when it's set on the object after it has
2679 show_pending_messages (chat
);
2681 /* check if a password is needed */
2682 chat_password_needed_changed_cb (chat
);
2686 empathy_chat_get_account (EmpathyChat
*chat
)
2688 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2690 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
2692 return priv
->account
;
2696 empathy_chat_get_id (EmpathyChat
*chat
)
2698 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2700 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
2706 empathy_chat_get_name (EmpathyChat
*chat
)
2708 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2711 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
2714 if (!ret
&& priv
->remote_contact
) {
2715 ret
= empathy_contact_get_name (priv
->remote_contact
);
2721 return ret
? ret
: _("Conversation");
2725 empathy_chat_get_subject (EmpathyChat
*chat
)
2727 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2729 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
2731 return priv
->subject
;
2735 empathy_chat_get_remote_contact (EmpathyChat
*chat
)
2737 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2739 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
2741 return priv
->remote_contact
;
2745 empathy_chat_get_contact_menu (EmpathyChat
*chat
)
2747 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2748 GtkWidget
*menu
= NULL
;
2750 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
2752 if (priv
->remote_contact
) {
2753 menu
= empathy_contact_menu_new (priv
->remote_contact
,
2754 EMPATHY_CONTACT_FEATURE_CALL
|
2755 EMPATHY_CONTACT_FEATURE_LOG
|
2756 EMPATHY_CONTACT_FEATURE_INFO
);
2758 else if (priv
->contact_list_view
) {
2759 EmpathyContactListView
*view
;
2761 view
= EMPATHY_CONTACT_LIST_VIEW (priv
->contact_list_view
);
2762 menu
= empathy_contact_list_view_get_contact_menu (view
);
2769 empathy_chat_clear (EmpathyChat
*chat
)
2771 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2773 empathy_chat_view_clear (chat
->view
);
2777 empathy_chat_scroll_down (EmpathyChat
*chat
)
2779 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2781 empathy_chat_view_scroll_down (chat
->view
);
2785 empathy_chat_cut (EmpathyChat
*chat
)
2787 GtkTextBuffer
*buffer
;
2789 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2791 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
2792 if (gtk_text_buffer_get_has_selection (buffer
)) {
2793 GtkClipboard
*clipboard
;
2795 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
2797 gtk_text_buffer_cut_clipboard (buffer
, clipboard
, TRUE
);
2802 empathy_chat_copy (EmpathyChat
*chat
)
2804 GtkTextBuffer
*buffer
;
2806 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2808 if (empathy_chat_view_get_has_selection (chat
->view
)) {
2809 empathy_chat_view_copy_clipboard (chat
->view
);
2813 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
2814 if (gtk_text_buffer_get_has_selection (buffer
)) {
2815 GtkClipboard
*clipboard
;
2817 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
2819 gtk_text_buffer_copy_clipboard (buffer
, clipboard
);
2824 empathy_chat_paste (EmpathyChat
*chat
)
2826 GtkTextBuffer
*buffer
;
2827 GtkClipboard
*clipboard
;
2828 EmpathyChatPriv
*priv
;
2830 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
2832 priv
= GET_PRIV (chat
);
2834 if (priv
->tp_chat
== NULL
||
2835 !GTK_WIDGET_IS_SENSITIVE (chat
->input_text_view
))
2838 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
2839 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
2841 gtk_text_buffer_paste_clipboard (buffer
, clipboard
, NULL
, TRUE
);
2845 empathy_chat_correct_word (EmpathyChat
*chat
,
2848 const gchar
*new_word
)
2850 GtkTextBuffer
*buffer
;
2852 g_return_if_fail (chat
!= NULL
);
2853 g_return_if_fail (new_word
!= NULL
);
2855 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
2857 gtk_text_buffer_delete (buffer
, start
, end
);
2858 gtk_text_buffer_insert (buffer
, start
,
2864 empathy_chat_is_room (EmpathyChat
*chat
)
2866 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
2868 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), FALSE
);
2870 return (priv
->handle_type
== TP_HANDLE_TYPE_ROOM
);
2874 empathy_chat_get_nb_unread_messages (EmpathyChat
*self
)
2876 EmpathyChatPriv
*priv
= GET_PRIV (self
);
2878 g_return_val_if_fail (EMPATHY_IS_CHAT (self
), FALSE
);
2880 return priv
->unread_messages
;
2883 /* called when the messages have been read by user */
2885 empathy_chat_messages_read (EmpathyChat
*self
)
2887 EmpathyChatPriv
*priv
= GET_PRIV (self
);
2889 g_return_if_fail (EMPATHY_IS_CHAT (self
));
2891 priv
->unread_messages
= 0;