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/util.h>
39 #include <libempathy/empathy-account-manager.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
;
73 EmpathyLogManager
*log_manager
;
74 EmpathyAccountManager
*account_manager
;
75 GSList
*sent_messages
;
76 gint sent_messages_index
;
78 GCompletion
*completion
;
79 guint composing_stop_timeout_id
;
80 guint block_events_timeout_id
;
81 TpHandleType handle_type
;
83 gboolean has_input_vscroll
;
88 GtkWidget
*scrolled_window_chat
;
89 GtkWidget
*scrolled_window_input
;
90 GtkWidget
*scrolled_window_contacts
;
91 GtkWidget
*hbox_topic
;
92 GtkWidget
*label_topic
;
93 GtkWidget
*contact_list_view
;
112 static guint signals
[LAST_SIGNAL
] = { 0 };
114 G_DEFINE_TYPE (EmpathyChat
, empathy_chat
, GTK_TYPE_BIN
);
117 chat_get_property (GObject
*object
,
122 EmpathyChat
*chat
= EMPATHY_CHAT (object
);
123 EmpathyChatPriv
*priv
= GET_PRIV (object
);
127 g_value_set_object (value
, priv
->tp_chat
);
130 g_value_set_object (value
, priv
->account
);
133 g_value_set_string (value
, empathy_chat_get_name (chat
));
136 g_value_set_string (value
, priv
->id
);
139 g_value_set_string (value
, priv
->subject
);
141 case PROP_REMOTE_CONTACT
:
142 g_value_set_object (value
, priv
->remote_contact
);
145 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
151 chat_set_property (GObject
*object
,
156 EmpathyChat
*chat
= EMPATHY_CHAT (object
);
160 empathy_chat_set_tp_chat (chat
, EMPATHY_TP_CHAT (g_value_get_object (value
)));
163 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
169 chat_connect_channel_reconnected (EmpathyDispatchOperation
*dispatch
,
173 EmpathyChat
*chat
= EMPATHY_CHAT (user_data
);
174 EmpathyTpChat
*tpchat
;
177 empathy_chat_view_append_event (chat
->view
,
178 _("Failed to reconnect this chat"));
182 tpchat
= EMPATHY_TP_CHAT (
183 empathy_dispatch_operation_get_channel_wrapper (dispatch
));
185 if (empathy_dispatch_operation_claim (dispatch
)) {
186 empathy_chat_set_tp_chat (chat
, tpchat
);
191 chat_new_connection_cb (EmpathyAccountManager
*manager
,
192 TpConnection
*connection
,
195 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
198 account
= empathy_account_manager_get_account (manager
, connection
);
199 if (!priv
->tp_chat
&& empathy_account_equal (account
, priv
->account
) &&
200 priv
->handle_type
!= TP_HANDLE_TYPE_NONE
&&
201 !EMP_STR_EMPTY (priv
->id
)) {
203 DEBUG ("Account reconnected, request a new Text channel");
205 switch (priv
->handle_type
) {
206 case TP_HANDLE_TYPE_CONTACT
:
207 empathy_dispatcher_chat_with_contact_id (
208 connection
, priv
->id
,
209 chat_connect_channel_reconnected
,
212 case TP_HANDLE_TYPE_ROOM
:
213 empathy_dispatcher_join_muc (connection
,
215 chat_connect_channel_reconnected
,
219 g_assert_not_reached ();
226 chat_composing_remove_timeout (EmpathyChat
*chat
)
228 EmpathyChatPriv
*priv
;
230 priv
= GET_PRIV (chat
);
232 if (priv
->composing_stop_timeout_id
) {
233 g_source_remove (priv
->composing_stop_timeout_id
);
234 priv
->composing_stop_timeout_id
= 0;
239 chat_composing_stop_timeout_cb (EmpathyChat
*chat
)
241 EmpathyChatPriv
*priv
;
243 priv
= GET_PRIV (chat
);
245 priv
->composing_stop_timeout_id
= 0;
246 empathy_tp_chat_set_state (priv
->tp_chat
,
247 TP_CHANNEL_CHAT_STATE_PAUSED
);
253 chat_composing_start (EmpathyChat
*chat
)
255 EmpathyChatPriv
*priv
;
257 priv
= GET_PRIV (chat
);
259 if (priv
->composing_stop_timeout_id
) {
260 /* Just restart the timeout */
261 chat_composing_remove_timeout (chat
);
263 empathy_tp_chat_set_state (priv
->tp_chat
,
264 TP_CHANNEL_CHAT_STATE_COMPOSING
);
267 priv
->composing_stop_timeout_id
= g_timeout_add_seconds (
268 COMPOSING_STOP_TIMEOUT
,
269 (GSourceFunc
) chat_composing_stop_timeout_cb
,
274 chat_composing_stop (EmpathyChat
*chat
)
276 EmpathyChatPriv
*priv
;
278 priv
= GET_PRIV (chat
);
280 chat_composing_remove_timeout (chat
);
281 empathy_tp_chat_set_state (priv
->tp_chat
,
282 TP_CHANNEL_CHAT_STATE_ACTIVE
);
286 chat_sent_message_add (EmpathyChat
*chat
,
289 EmpathyChatPriv
*priv
;
293 priv
= GET_PRIV (chat
);
295 /* Save the sent message in our repeat buffer */
296 list
= priv
->sent_messages
;
298 /* Remove any other occurances of this msg */
299 while ((item
= g_slist_find_custom (list
, str
, (GCompareFunc
) strcmp
)) != NULL
) {
300 list
= g_slist_remove_link (list
, item
);
302 g_slist_free1 (item
);
305 /* Trim the list to the last 10 items */
306 while (g_slist_length (list
) > 10) {
307 item
= g_slist_last (list
);
309 list
= g_slist_remove_link (list
, item
);
311 g_slist_free1 (item
);
315 /* Add new message */
316 list
= g_slist_prepend (list
, g_strdup (str
));
318 /* Set list and reset the index */
319 priv
->sent_messages
= list
;
320 priv
->sent_messages_index
= -1;
324 chat_sent_message_get_next (EmpathyChat
*chat
)
326 EmpathyChatPriv
*priv
;
329 priv
= GET_PRIV (chat
);
331 if (!priv
->sent_messages
) {
332 DEBUG ("No sent messages, next message is NULL");
336 max
= g_slist_length (priv
->sent_messages
) - 1;
338 if (priv
->sent_messages_index
< max
) {
339 priv
->sent_messages_index
++;
342 DEBUG ("Returning next message index:%d", priv
->sent_messages_index
);
344 return g_slist_nth_data (priv
->sent_messages
, priv
->sent_messages_index
);
348 chat_sent_message_get_last (EmpathyChat
*chat
)
350 EmpathyChatPriv
*priv
;
352 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
354 priv
= GET_PRIV (chat
);
356 if (!priv
->sent_messages
) {
357 DEBUG ("No sent messages, last message is NULL");
361 if (priv
->sent_messages_index
>= 0) {
362 priv
->sent_messages_index
--;
365 DEBUG ("Returning last message index:%d", priv
->sent_messages_index
);
367 return g_slist_nth_data (priv
->sent_messages
, priv
->sent_messages_index
);
371 chat_send (EmpathyChat
*chat
,
374 EmpathyChatPriv
*priv
;
375 EmpathyMessage
*message
;
377 if (EMP_STR_EMPTY (msg
)) {
381 priv
= GET_PRIV (chat
);
383 chat_sent_message_add (chat
, msg
);
385 if (g_str_has_prefix (msg
, "/clear")) {
386 empathy_chat_view_clear (chat
->view
);
390 /* Blacklist messages begining by '/', except for "/me" and "/say"
391 * because they are handled in EmpathyMessage */
393 !g_str_has_prefix (msg
, "/me") &&
394 !g_str_has_prefix (msg
, "/say")) {
395 empathy_chat_view_append_event (chat
->view
,
396 _("Unsupported command"));
400 /* We can send the message */
401 message
= empathy_message_new (msg
);
402 empathy_tp_chat_send (priv
->tp_chat
, message
);
403 g_object_unref (message
);
407 chat_input_text_view_send (EmpathyChat
*chat
)
409 EmpathyChatPriv
*priv
;
410 GtkTextBuffer
*buffer
;
411 GtkTextIter start
, end
;
414 priv
= GET_PRIV (chat
);
416 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
418 gtk_text_buffer_get_bounds (buffer
, &start
, &end
);
419 msg
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
421 /* clear the input field */
422 gtk_text_buffer_set_text (buffer
, "", -1);
424 chat_send (chat
, msg
);
429 chat_state_changed_cb (EmpathyTpChat
*tp_chat
,
430 EmpathyContact
*contact
,
431 TpChannelChatState state
,
434 EmpathyChatPriv
*priv
;
436 gboolean was_composing
;
438 priv
= GET_PRIV (chat
);
440 if (empathy_contact_is_user (contact
)) {
441 /* We don't care about our own chat state */
445 was_composing
= (priv
->compositors
!= NULL
);
447 /* Find the contact in the list. After that l is the list elem or NULL */
448 for (l
= priv
->compositors
; l
; l
= l
->next
) {
449 if (contact
== l
->data
) {
455 case TP_CHANNEL_CHAT_STATE_GONE
:
456 case TP_CHANNEL_CHAT_STATE_INACTIVE
:
457 case TP_CHANNEL_CHAT_STATE_PAUSED
:
458 case TP_CHANNEL_CHAT_STATE_ACTIVE
:
459 /* Contact is not composing */
461 priv
->compositors
= g_list_remove_link (priv
->compositors
, l
);
462 g_object_unref (l
->data
);
466 case TP_CHANNEL_CHAT_STATE_COMPOSING
:
467 /* Contact is composing */
469 priv
->compositors
= g_list_prepend (priv
->compositors
,
470 g_object_ref (contact
));
474 g_assert_not_reached ();
477 DEBUG ("Was composing: %s now composing: %s",
478 was_composing
? "yes" : "no",
479 priv
->compositors
? "yes" : "no");
481 if ((was_composing
&& !priv
->compositors
) ||
482 (!was_composing
&& priv
->compositors
)) {
483 /* Composing state changed */
484 g_signal_emit (chat
, signals
[COMPOSING
], 0,
485 priv
->compositors
!= NULL
);
490 chat_message_received (EmpathyChat
*chat
, EmpathyMessage
*message
)
492 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
493 EmpathyContact
*sender
;
495 sender
= empathy_message_get_sender (message
);
497 DEBUG ("Appending new message from %s (%d)",
498 empathy_contact_get_name (sender
),
499 empathy_contact_get_handle (sender
));
501 empathy_chat_view_append_message (chat
->view
, message
);
503 /* We received a message so the contact is no longer composing */
504 chat_state_changed_cb (priv
->tp_chat
, sender
,
505 TP_CHANNEL_CHAT_STATE_ACTIVE
,
508 g_signal_emit (chat
, signals
[NEW_MESSAGE
], 0, message
);
512 chat_message_received_cb (EmpathyTpChat
*tp_chat
,
513 EmpathyMessage
*message
,
516 chat_message_received (chat
, message
);
517 empathy_tp_chat_acknowledge_message (tp_chat
, message
);
521 chat_send_error_cb (EmpathyTpChat
*tp_chat
,
522 EmpathyMessage
*message
,
523 TpChannelTextSendError error_code
,
529 switch (error_code
) {
530 case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE
:
531 error
= _("offline");
533 case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT
:
534 error
= _("invalid contact");
536 case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED
:
537 error
= _("permission denied");
539 case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG
:
540 error
= _("too long message");
542 case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED
:
543 error
= _("not implemented");
546 error
= _("unknown");
550 str
= g_strdup_printf (_("Error sending message '%s': %s"),
551 empathy_message_get_body (message
),
553 empathy_chat_view_append_event (chat
->view
, str
);
558 chat_property_changed_cb (EmpathyTpChat
*tp_chat
,
563 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
565 if (!tp_strdiff (name
, "subject")) {
566 g_free (priv
->subject
);
567 priv
->subject
= g_value_dup_string (value
);
568 g_object_notify (G_OBJECT (chat
), "subject");
570 if (EMP_STR_EMPTY (priv
->subject
)) {
571 gtk_widget_hide (priv
->hbox_topic
);
573 gtk_label_set_text (GTK_LABEL (priv
->label_topic
), priv
->subject
);
574 gtk_widget_show (priv
->hbox_topic
);
576 if (priv
->block_events_timeout_id
== 0) {
579 if (!EMP_STR_EMPTY (priv
->subject
)) {
580 str
= g_strdup_printf (_("Topic set to: %s"), priv
->subject
);
582 str
= g_strdup (_("No topic defined"));
584 empathy_chat_view_append_event (EMPATHY_CHAT (chat
)->view
, str
);
588 else if (!tp_strdiff (name
, "name")) {
590 priv
->name
= g_value_dup_string (value
);
591 g_object_notify (G_OBJECT (chat
), "name");
596 chat_input_text_buffer_changed_cb (GtkTextBuffer
*buffer
,
599 EmpathyChatPriv
*priv
;
600 GtkTextIter start
, end
;
602 gboolean spell_checker
= FALSE
;
604 priv
= GET_PRIV (chat
);
606 if (gtk_text_buffer_get_char_count (buffer
) == 0) {
607 chat_composing_stop (chat
);
609 chat_composing_start (chat
);
612 empathy_conf_get_bool (empathy_conf_get (),
613 EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED
,
616 gtk_text_buffer_get_start_iter (buffer
, &start
);
618 if (!spell_checker
) {
619 gtk_text_buffer_get_end_iter (buffer
, &end
);
620 gtk_text_buffer_remove_tag_by_name (buffer
, "misspelled", &start
, &end
);
624 if (!empathy_spell_supported ()) {
628 /* NOTE: this is really inefficient, we shouldn't have to
629 reiterate the whole buffer each time and check each work
632 gboolean correct
= FALSE
;
635 if (gtk_text_iter_is_start (&start
)) {
638 if (!gtk_text_iter_forward_word_end (&end
)) {
639 /* no whole word yet */
643 if (!gtk_text_iter_forward_word_end (&end
)) {
644 /* must be the end of the buffer */
649 gtk_text_iter_backward_word_start (&start
);
652 str
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
654 /* spell check string if not a command */
656 correct
= empathy_spell_check (str
);
662 gtk_text_buffer_apply_tag_by_name (buffer
, "misspelled", &start
, &end
);
664 gtk_text_buffer_remove_tag_by_name (buffer
, "misspelled", &start
, &end
);
669 /* set start iter to the end iters position */
675 chat_input_key_press_event_cb (GtkWidget
*widget
,
679 EmpathyChatPriv
*priv
;
682 GtkWidget
*text_view_sw
;
684 priv
= GET_PRIV (chat
);
686 /* Catch ctrl+up/down so we can traverse messages we sent */
687 if ((event
->state
& GDK_CONTROL_MASK
) &&
688 (event
->keyval
== GDK_Up
||
689 event
->keyval
== GDK_Down
)) {
690 GtkTextBuffer
*buffer
;
693 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
695 if (event
->keyval
== GDK_Up
) {
696 str
= chat_sent_message_get_next (chat
);
698 str
= chat_sent_message_get_last (chat
);
701 g_signal_handlers_block_by_func (buffer
,
702 chat_input_text_buffer_changed_cb
,
704 gtk_text_buffer_set_text (buffer
, str
? str
: "", -1);
705 g_signal_handlers_unblock_by_func (buffer
,
706 chat_input_text_buffer_changed_cb
,
712 /* Catch enter but not ctrl/shift-enter */
713 if (IS_ENTER (event
->keyval
) &&
714 !(event
->state
& (GDK_SHIFT_MASK
| GDK_CONTROL_MASK
))) {
717 /* This is to make sure that kinput2 gets the enter. And if
718 * it's handled there we shouldn't send on it. This is because
719 * kinput2 uses Enter to commit letters. See:
720 * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
723 view
= GTK_TEXT_VIEW (chat
->input_text_view
);
724 if (gtk_im_context_filter_keypress (view
->im_context
, event
)) {
725 GTK_TEXT_VIEW (chat
->input_text_view
)->need_im_reset
= TRUE
;
729 chat_input_text_view_send (chat
);
733 text_view_sw
= gtk_widget_get_parent (GTK_WIDGET (chat
->view
));
735 if (IS_ENTER (event
->keyval
) &&
736 (event
->state
& (GDK_SHIFT_MASK
| GDK_CONTROL_MASK
))) {
737 /* Newline for shift/control-enter. */
740 if (!(event
->state
& GDK_CONTROL_MASK
) &&
741 event
->keyval
== GDK_Page_Up
) {
742 adj
= gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw
));
743 gtk_adjustment_set_value (adj
, adj
->value
- adj
->page_size
);
746 if ((event
->state
& GDK_CONTROL_MASK
) != GDK_CONTROL_MASK
&&
747 event
->keyval
== GDK_Page_Down
) {
748 adj
= gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw
));
749 val
= MIN (adj
->value
+ adj
->page_size
, adj
->upper
- adj
->page_size
);
750 gtk_adjustment_set_value (adj
, val
);
753 if (!(event
->state
& (GDK_CONTROL_MASK
| GDK_SHIFT_MASK
)) &&
754 event
->keyval
== GDK_Tab
) {
755 GtkTextBuffer
*buffer
;
756 GtkTextIter start
, current
;
757 gchar
*nick
, *completed
;
758 GList
*list
, *completed_list
;
759 gboolean is_start_of_buffer
;
761 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (EMPATHY_CHAT (chat
)->input_text_view
));
762 gtk_text_buffer_get_iter_at_mark (buffer
, ¤t
, gtk_text_buffer_get_insert (buffer
));
764 /* Get the start of the nick to complete. */
765 gtk_text_buffer_get_iter_at_mark (buffer
, &start
, gtk_text_buffer_get_insert (buffer
));
766 gtk_text_iter_backward_word_start (&start
);
767 is_start_of_buffer
= gtk_text_iter_is_start (&start
);
769 list
= empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (priv
->tp_chat
));
770 g_completion_add_items (priv
->completion
, list
);
772 nick
= gtk_text_buffer_get_text (buffer
, &start
, ¤t
, FALSE
);
773 completed_list
= g_completion_complete (priv
->completion
,
782 gchar
*complete_char
= NULL
;
784 gtk_text_buffer_delete (buffer
, &start
, ¤t
);
786 len
= g_list_length (completed_list
);
789 /* If we only have one hit, use that text
790 * instead of the text in completed since the
791 * completed text will use the typed string
792 * which might be cased all wrong.
795 text
= empathy_contact_get_name (completed_list
->data
);
800 gtk_text_buffer_insert_at_cursor (buffer
, text
, strlen (text
));
802 if (len
== 1 && is_start_of_buffer
&&
803 empathy_conf_get_string (empathy_conf_get (),
804 EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR
,
806 complete_char
!= NULL
) {
807 gtk_text_buffer_insert_at_cursor (buffer
,
809 strlen (complete_char
));
810 gtk_text_buffer_insert_at_cursor (buffer
, " ", 1);
811 g_free (complete_char
);
817 g_completion_clear_items (priv
->completion
);
819 g_list_foreach (list
, (GFunc
) g_object_unref
, NULL
);
829 chat_text_view_focus_in_event_cb (GtkWidget
*widget
,
833 gtk_widget_grab_focus (chat
->input_text_view
);
839 chat_input_set_size_request_idle (gpointer sw
)
841 gtk_widget_set_size_request (sw
, -1, MAX_INPUT_HEIGHT
);
847 chat_input_size_request_cb (GtkWidget
*widget
,
848 GtkRequisition
*requisition
,
851 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
854 sw
= gtk_widget_get_parent (widget
);
855 if (requisition
->height
>= MAX_INPUT_HEIGHT
&& !priv
->has_input_vscroll
) {
856 g_idle_add (chat_input_set_size_request_idle
, sw
);
857 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
860 priv
->has_input_vscroll
= TRUE
;
863 if (requisition
->height
< MAX_INPUT_HEIGHT
&& priv
->has_input_vscroll
) {
864 gtk_widget_set_size_request (sw
, -1, -1);
865 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
868 priv
->has_input_vscroll
= FALSE
;
873 chat_input_realize_cb (GtkWidget
*widget
,
876 DEBUG ("Setting focus to the input text view");
877 gtk_widget_grab_focus (widget
);
881 chat_insert_smiley_activate_cb (EmpathySmileyManager
*manager
,
882 EmpathySmiley
*smiley
,
885 EmpathyChat
*chat
= EMPATHY_CHAT (user_data
);
886 GtkTextBuffer
*buffer
;
889 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
891 gtk_text_buffer_get_end_iter (buffer
, &iter
);
892 gtk_text_buffer_insert (buffer
, &iter
, smiley
->str
, -1);
894 gtk_text_buffer_get_end_iter (buffer
, &iter
);
895 gtk_text_buffer_insert (buffer
, &iter
, " ", -1);
906 static EmpathyChatSpell
*
907 chat_spell_new (EmpathyChat
*chat
,
912 EmpathyChatSpell
*chat_spell
;
914 chat_spell
= g_slice_new0 (EmpathyChatSpell
);
916 chat_spell
->chat
= g_object_ref (chat
);
917 chat_spell
->word
= g_strdup (word
);
918 chat_spell
->start
= start
;
919 chat_spell
->end
= end
;
925 chat_spell_free (EmpathyChatSpell
*chat_spell
)
927 g_object_unref (chat_spell
->chat
);
928 g_free (chat_spell
->word
);
929 g_slice_free (EmpathyChatSpell
, chat_spell
);
933 chat_spelling_menu_activate_cb (GtkMenuItem
*menu_item
,
934 EmpathyChatSpell
*chat_spell
)
936 empathy_chat_correct_word (chat_spell
->chat
,
937 &(chat_spell
->start
),
939 gtk_menu_item_get_label (menu_item
));
943 chat_spelling_build_menu (EmpathyChatSpell
*chat_spell
)
945 GtkWidget
*menu
, *menu_item
;
946 GList
*suggestions
, *l
;
948 menu
= gtk_menu_new ();
949 suggestions
= empathy_spell_get_suggestions (chat_spell
->word
);
950 if (suggestions
== NULL
) {
951 menu_item
= gtk_menu_item_new_with_label (_("(No Suggestions)"));
952 gtk_widget_set_sensitive (menu_item
, FALSE
);
953 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), menu_item
);
955 for (l
= suggestions
; l
; l
= l
->next
) {
956 menu_item
= gtk_menu_item_new_with_label (l
->data
);
957 g_signal_connect (G_OBJECT (menu_item
),
959 G_CALLBACK (chat_spelling_menu_activate_cb
),
961 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), menu_item
);
964 empathy_spell_free_suggestions (suggestions
);
966 gtk_widget_show_all (menu
);
972 chat_text_send_cb (GtkMenuItem
*menuitem
,
975 chat_input_text_view_send (chat
);
979 chat_input_populate_popup_cb (GtkTextView
*view
,
983 EmpathyChatPriv
*priv
;
984 GtkTextBuffer
*buffer
;
985 GtkTextTagTable
*table
;
988 GtkTextIter iter
, start
, end
;
991 EmpathyChatSpell
*chat_spell
;
992 GtkWidget
*spell_menu
;
993 EmpathySmileyManager
*smiley_manager
;
994 GtkWidget
*smiley_menu
;
997 priv
= GET_PRIV (chat
);
998 buffer
= gtk_text_view_get_buffer (view
);
1000 /* Add the emoticon menu. */
1001 item
= gtk_separator_menu_item_new ();
1002 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1003 gtk_widget_show (item
);
1005 item
= gtk_image_menu_item_new_with_mnemonic (_("Insert Smiley"));
1006 image
= gtk_image_new_from_icon_name ("face-smile",
1007 GTK_ICON_SIZE_MENU
);
1008 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item
), image
);
1009 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1010 gtk_widget_show (item
);
1012 smiley_manager
= empathy_smiley_manager_dup_singleton ();
1013 smiley_menu
= empathy_smiley_menu_new (smiley_manager
,
1014 chat_insert_smiley_activate_cb
,
1016 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item
), smiley_menu
);
1017 g_object_unref (smiley_manager
);
1019 /* Add the Send menu item. */
1020 gtk_text_buffer_get_bounds (buffer
, &start
, &end
);
1021 str
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
1022 if (!EMP_STR_EMPTY (str
)) {
1023 item
= gtk_menu_item_new_with_mnemonic (_("_Send"));
1024 g_signal_connect (G_OBJECT (item
), "activate",
1025 G_CALLBACK (chat_text_send_cb
), chat
);
1026 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1027 gtk_widget_show (item
);
1031 /* Add the spell check menu item. */
1032 table
= gtk_text_buffer_get_tag_table (buffer
);
1033 tag
= gtk_text_tag_table_lookup (table
, "misspelled");
1034 gtk_widget_get_pointer (GTK_WIDGET (view
), &x
, &y
);
1035 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view
),
1036 GTK_TEXT_WINDOW_WIDGET
,
1039 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view
), &iter
, x
, y
);
1041 if (gtk_text_iter_backward_to_tag_toggle (&start
, tag
) &&
1042 gtk_text_iter_forward_to_tag_toggle (&end
, tag
)) {
1044 str
= gtk_text_buffer_get_text (buffer
,
1045 &start
, &end
, FALSE
);
1047 if (!EMP_STR_EMPTY (str
)) {
1048 chat_spell
= chat_spell_new (chat
, str
, start
, end
);
1049 g_object_set_data_full (G_OBJECT (menu
),
1050 "chat_spell", chat_spell
,
1051 (GDestroyNotify
) chat_spell_free
);
1053 item
= gtk_separator_menu_item_new ();
1054 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1055 gtk_widget_show (item
);
1057 item
= gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
1058 image
= gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK
,
1059 GTK_ICON_SIZE_MENU
);
1060 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item
), image
);
1062 spell_menu
= chat_spelling_build_menu (chat_spell
);
1063 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item
), spell_menu
);
1065 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
1066 gtk_widget_show (item
);
1071 chat_log_filter (EmpathyMessage
*message
,
1074 EmpathyChat
*chat
= (EmpathyChat
*) user_data
;
1075 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1076 const GList
*pending
;
1078 pending
= empathy_tp_chat_get_pending_messages (priv
->tp_chat
);
1080 for (; pending
; pending
= g_list_next (pending
)) {
1081 if (empathy_message_equal (message
, pending
->data
)) {
1090 chat_add_logs (EmpathyChat
*chat
)
1092 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1093 gboolean is_chatroom
;
1094 GList
*messages
, *l
;
1100 /* Turn off scrolling temporarily */
1101 empathy_chat_view_scroll (chat
->view
, FALSE
);
1103 /* Add messages from last conversation */
1104 is_chatroom
= priv
->handle_type
== TP_HANDLE_TYPE_ROOM
;
1106 messages
= empathy_log_manager_get_filtered_messages (priv
->log_manager
,
1114 for (l
= messages
; l
; l
= g_list_next (l
)) {
1115 empathy_chat_view_append_message (chat
->view
, l
->data
);
1116 g_object_unref (l
->data
);
1119 g_list_free (messages
);
1121 /* Turn back on scrolling */
1122 empathy_chat_view_scroll (chat
->view
, TRUE
);
1126 chat_contacts_completion_func (const gchar
*s1
,
1130 gchar
*tmp
, *nick1
, *nick2
;
1137 return s1
? -1 : +1;
1140 tmp
= g_utf8_normalize (s1
, -1, G_NORMALIZE_DEFAULT
);
1141 nick1
= g_utf8_casefold (tmp
, -1);
1144 tmp
= g_utf8_normalize (s2
, -1, G_NORMALIZE_DEFAULT
);
1145 nick2
= g_utf8_casefold (tmp
, -1);
1148 ret
= strncmp (nick1
, nick2
, n
);
1157 chat_members_changed_cb (EmpathyTpChat
*tp_chat
,
1158 EmpathyContact
*contact
,
1159 EmpathyContact
*actor
,
1165 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1167 if (priv
->block_events_timeout_id
== 0) {
1171 str
= g_strdup_printf (_("%s has joined the room"),
1172 empathy_contact_get_name (contact
));
1174 str
= g_strdup_printf (_("%s has left the room"),
1175 empathy_contact_get_name (contact
));
1177 empathy_chat_view_append_event (chat
->view
, str
);
1183 chat_reset_size_request (gpointer widget
)
1185 gtk_widget_set_size_request (widget
, -1, -1);
1191 chat_set_show_contacts (EmpathyChat
*chat
, gboolean show
)
1193 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1195 if (!priv
->scrolled_window_contacts
) {
1200 EmpathyContactListStore
*store
;
1203 /* We are adding the contact list to the chat, we don't want the
1204 * chat view to become too small. If the chat view is already
1205 * smaller than 250 make sure that size won't change. If the
1206 * chat view is bigger the contact list will take some space on
1207 * it but we make sure the chat view don't become smaller than
1208 * 250. Relax the size request once the resize is done */
1209 min_width
= MIN (priv
->vbox_left
->allocation
.width
, 250);
1210 gtk_widget_set_size_request (priv
->vbox_left
, min_width
, -1);
1211 g_idle_add (chat_reset_size_request
, priv
->vbox_left
);
1213 if (priv
->contacts_width
> 0) {
1214 gtk_paned_set_position (GTK_PANED (priv
->hpaned
),
1215 priv
->contacts_width
);
1218 store
= empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv
->tp_chat
));
1219 priv
->contact_list_view
= GTK_WIDGET (empathy_contact_list_view_new (store
,
1220 EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP
,
1221 EMPATHY_CONTACT_FEATURE_CHAT
|
1222 EMPATHY_CONTACT_FEATURE_CALL
|
1223 EMPATHY_CONTACT_FEATURE_LOG
|
1224 EMPATHY_CONTACT_FEATURE_INFO
));
1225 gtk_container_add (GTK_CONTAINER (priv
->scrolled_window_contacts
),
1226 priv
->contact_list_view
);
1227 gtk_widget_show (priv
->contact_list_view
);
1228 gtk_widget_show (priv
->scrolled_window_contacts
);
1229 g_object_unref (store
);
1231 priv
->contacts_width
= gtk_paned_get_position (GTK_PANED (priv
->hpaned
));
1232 gtk_widget_hide (priv
->scrolled_window_contacts
);
1233 if (priv
->contact_list_view
) {
1234 gtk_widget_destroy (priv
->contact_list_view
);
1235 priv
->contact_list_view
= NULL
;
1241 chat_remote_contact_changed_cb (EmpathyChat
*chat
)
1243 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1245 if (priv
->remote_contact
) {
1246 g_object_unref (priv
->remote_contact
);
1247 priv
->remote_contact
= NULL
;
1250 priv
->remote_contact
= empathy_tp_chat_get_remote_contact (priv
->tp_chat
);
1251 if (priv
->remote_contact
) {
1252 g_object_ref (priv
->remote_contact
);
1253 priv
->handle_type
= TP_HANDLE_TYPE_CONTACT
;
1255 priv
->id
= g_strdup (empathy_contact_get_id (priv
->remote_contact
));
1257 else if (priv
->tp_chat
) {
1260 channel
= empathy_tp_chat_get_channel (priv
->tp_chat
);
1261 g_object_get (channel
, "handle-type", &priv
->handle_type
, NULL
);
1263 priv
->id
= g_strdup (empathy_tp_chat_get_id (priv
->tp_chat
));
1266 chat_set_show_contacts (chat
, priv
->remote_contact
== NULL
);
1268 g_object_notify (G_OBJECT (chat
), "remote-contact");
1269 g_object_notify (G_OBJECT (chat
), "id");
1273 chat_destroy_cb (EmpathyTpChat
*tp_chat
,
1276 EmpathyChatPriv
*priv
;
1278 priv
= GET_PRIV (chat
);
1280 if (!priv
->tp_chat
) {
1284 chat_composing_remove_timeout (chat
);
1285 g_object_unref (priv
->tp_chat
);
1286 priv
->tp_chat
= NULL
;
1287 g_object_notify (G_OBJECT (chat
), "tp-chat");
1289 empathy_chat_view_append_event (chat
->view
, _("Disconnected"));
1290 gtk_widget_set_sensitive (chat
->input_text_view
, FALSE
);
1291 chat_set_show_contacts (chat
, FALSE
);
1295 show_pending_messages (EmpathyChat
*chat
) {
1296 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1297 const GList
*messages
, *l
;
1299 if (chat
->view
== NULL
|| priv
->tp_chat
== NULL
)
1302 messages
= empathy_tp_chat_get_pending_messages (priv
->tp_chat
);
1304 for (l
= messages
; l
!= NULL
; l
= g_list_next (l
)) {
1305 EmpathyMessage
*message
= EMPATHY_MESSAGE (l
->data
);
1306 chat_message_received (chat
, message
);
1308 empathy_tp_chat_acknowledge_messages (priv
->tp_chat
, messages
);
1312 chat_create_ui (EmpathyChat
*chat
)
1314 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1318 GtkTextBuffer
*buffer
;
1320 filename
= empathy_file_lookup ("empathy-chat.ui",
1322 gui
= empathy_builder_get_file (filename
,
1323 "chat_widget", &priv
->widget
,
1324 "hpaned", &priv
->hpaned
,
1325 "vbox_left", &priv
->vbox_left
,
1326 "scrolled_window_chat", &priv
->scrolled_window_chat
,
1327 "scrolled_window_input", &priv
->scrolled_window_input
,
1328 "hbox_topic", &priv
->hbox_topic
,
1329 "label_topic", &priv
->label_topic
,
1330 "scrolled_window_contacts", &priv
->scrolled_window_contacts
,
1334 /* Add message view. */
1335 chat
->view
= empathy_theme_manager_create_view (empathy_theme_manager_get ());
1336 g_signal_connect (chat
->view
, "focus_in_event",
1337 G_CALLBACK (chat_text_view_focus_in_event_cb
),
1339 gtk_container_add (GTK_CONTAINER (priv
->scrolled_window_chat
),
1340 GTK_WIDGET (chat
->view
));
1341 gtk_widget_show (GTK_WIDGET (chat
->view
));
1343 /* Add input GtkTextView */
1344 chat
->input_text_view
= g_object_new (GTK_TYPE_TEXT_VIEW
,
1345 "pixels-above-lines", 2,
1346 "pixels-below-lines", 2,
1347 "pixels-inside-wrap", 1,
1350 "wrap-mode", GTK_WRAP_WORD_CHAR
,
1352 g_signal_connect (chat
->input_text_view
, "key-press-event",
1353 G_CALLBACK (chat_input_key_press_event_cb
),
1355 g_signal_connect (chat
->input_text_view
, "size-request",
1356 G_CALLBACK (chat_input_size_request_cb
),
1358 g_signal_connect (chat
->input_text_view
, "realize",
1359 G_CALLBACK (chat_input_realize_cb
),
1361 g_signal_connect (chat
->input_text_view
, "populate-popup",
1362 G_CALLBACK (chat_input_populate_popup_cb
),
1364 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1365 g_signal_connect (buffer
, "changed",
1366 G_CALLBACK (chat_input_text_buffer_changed_cb
),
1368 gtk_text_buffer_create_tag (buffer
, "misspelled",
1369 "underline", PANGO_UNDERLINE_ERROR
,
1371 gtk_container_add (GTK_CONTAINER (priv
->scrolled_window_input
),
1372 chat
->input_text_view
);
1373 gtk_widget_show (chat
->input_text_view
);
1375 /* Create contact list */
1376 chat_set_show_contacts (chat
, priv
->remote_contact
== NULL
);
1378 /* Initialy hide the topic, will be shown if not empty */
1379 gtk_widget_hide (priv
->hbox_topic
);
1381 /* Set widget focus order */
1382 list
= g_list_append (NULL
, priv
->scrolled_window_input
);
1383 gtk_container_set_focus_chain (GTK_CONTAINER (priv
->vbox_left
), list
);
1386 list
= g_list_append (NULL
, priv
->vbox_left
);
1387 list
= g_list_append (list
, priv
->scrolled_window_contacts
);
1388 gtk_container_set_focus_chain (GTK_CONTAINER (priv
->hpaned
), list
);
1391 list
= g_list_append (NULL
, priv
->hpaned
);
1392 list
= g_list_append (list
, priv
->hbox_topic
);
1393 gtk_container_set_focus_chain (GTK_CONTAINER (priv
->widget
), list
);
1396 /* Add the main widget in the chat widget */
1397 gtk_container_add (GTK_CONTAINER (chat
), priv
->widget
);
1398 g_object_unref (gui
);
1402 chat_size_request (GtkWidget
*widget
,
1403 GtkRequisition
*requisition
)
1405 GtkBin
*bin
= GTK_BIN (widget
);
1407 requisition
->width
= GTK_CONTAINER (widget
)->border_width
* 2;
1408 requisition
->height
= GTK_CONTAINER (widget
)->border_width
* 2;
1410 if (bin
->child
&& GTK_WIDGET_VISIBLE (bin
->child
))
1412 GtkRequisition child_requisition
;
1414 gtk_widget_size_request (bin
->child
, &child_requisition
);
1416 requisition
->width
+= child_requisition
.width
;
1417 requisition
->height
+= child_requisition
.height
;
1422 chat_size_allocate (GtkWidget
*widget
,
1423 GtkAllocation
*allocation
)
1425 GtkBin
*bin
= GTK_BIN (widget
);
1426 GtkAllocation child_allocation
;
1428 widget
->allocation
= *allocation
;
1430 if (bin
->child
&& GTK_WIDGET_VISIBLE (bin
->child
))
1432 child_allocation
.x
= allocation
->x
+ GTK_CONTAINER (widget
)->border_width
;
1433 child_allocation
.y
= allocation
->y
+ GTK_CONTAINER (widget
)->border_width
;
1434 child_allocation
.width
= MAX (allocation
->width
- GTK_CONTAINER (widget
)->border_width
* 2, 0);
1435 child_allocation
.height
= MAX (allocation
->height
- GTK_CONTAINER (widget
)->border_width
* 2, 0);
1437 gtk_widget_size_allocate (bin
->child
, &child_allocation
);
1442 chat_finalize (GObject
*object
)
1445 EmpathyChatPriv
*priv
;
1447 chat
= EMPATHY_CHAT (object
);
1448 priv
= GET_PRIV (chat
);
1450 DEBUG ("Finalized: %p", object
);
1452 g_slist_foreach (priv
->sent_messages
, (GFunc
) g_free
, NULL
);
1453 g_slist_free (priv
->sent_messages
);
1455 g_list_foreach (priv
->compositors
, (GFunc
) g_object_unref
, NULL
);
1456 g_list_free (priv
->compositors
);
1458 chat_composing_remove_timeout (chat
);
1460 g_signal_handlers_disconnect_by_func (priv
->account_manager
,
1461 chat_new_connection_cb
, object
);
1463 g_object_unref (priv
->account_manager
);
1464 g_object_unref (priv
->log_manager
);
1466 if (priv
->tp_chat
) {
1467 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1468 chat_destroy_cb
, chat
);
1469 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1470 chat_message_received_cb
, chat
);
1471 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1472 chat_send_error_cb
, chat
);
1473 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1474 chat_state_changed_cb
, chat
);
1475 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1476 chat_property_changed_cb
, chat
);
1477 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1478 chat_members_changed_cb
, chat
);
1479 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1480 chat_remote_contact_changed_cb
, chat
);
1481 empathy_tp_chat_close (priv
->tp_chat
);
1482 g_object_unref (priv
->tp_chat
);
1484 if (priv
->account
) {
1485 g_object_unref (priv
->account
);
1487 if (priv
->remote_contact
) {
1488 g_object_unref (priv
->remote_contact
);
1491 if (priv
->block_events_timeout_id
) {
1492 g_source_remove (priv
->block_events_timeout_id
);
1496 g_free (priv
->name
);
1497 g_free (priv
->subject
);
1498 g_completion_free (priv
->completion
);
1500 G_OBJECT_CLASS (empathy_chat_parent_class
)->finalize (object
);
1504 chat_constructed (GObject
*object
)
1506 EmpathyChat
*chat
= EMPATHY_CHAT (object
);
1508 chat_create_ui (chat
);
1509 chat_add_logs (chat
);
1510 show_pending_messages (chat
);
1514 empathy_chat_class_init (EmpathyChatClass
*klass
)
1516 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS (klass
);
1517 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
1519 object_class
->finalize
= chat_finalize
;
1520 object_class
->get_property
= chat_get_property
;
1521 object_class
->set_property
= chat_set_property
;
1522 object_class
->constructed
= chat_constructed
;
1524 widget_class
->size_request
= chat_size_request
;
1525 widget_class
->size_allocate
= chat_size_allocate
;
1527 g_object_class_install_property (object_class
,
1529 g_param_spec_object ("tp-chat",
1531 "The tp chat object",
1532 EMPATHY_TYPE_TP_CHAT
,
1534 G_PARAM_READWRITE
));
1535 g_object_class_install_property (object_class
,
1537 g_param_spec_object ("account",
1538 "Account of the chat",
1539 "The account of the chat",
1542 g_object_class_install_property (object_class
,
1544 g_param_spec_string ("id",
1546 "The id of the chat",
1549 g_object_class_install_property (object_class
,
1551 g_param_spec_string ("name",
1553 "The name of the chat",
1556 g_object_class_install_property (object_class
,
1558 g_param_spec_string ("subject",
1560 "The subject or topic of the chat",
1563 g_object_class_install_property (object_class
,
1564 PROP_REMOTE_CONTACT
,
1565 g_param_spec_object ("remote-contact",
1566 "The remote contact",
1567 "The remote contact is any",
1568 EMPATHY_TYPE_CONTACT
,
1571 signals
[COMPOSING
] =
1572 g_signal_new ("composing",
1573 G_OBJECT_CLASS_TYPE (object_class
),
1577 g_cclosure_marshal_VOID__BOOLEAN
,
1581 signals
[NEW_MESSAGE
] =
1582 g_signal_new ("new-message",
1583 G_OBJECT_CLASS_TYPE (object_class
),
1587 g_cclosure_marshal_VOID__OBJECT
,
1589 1, EMPATHY_TYPE_MESSAGE
);
1591 g_type_class_add_private (object_class
, sizeof (EmpathyChatPriv
));
1595 chat_block_events_timeout_cb (gpointer data
)
1597 EmpathyChatPriv
*priv
= GET_PRIV (data
);
1599 priv
->block_events_timeout_id
= 0;
1605 empathy_chat_init (EmpathyChat
*chat
)
1607 EmpathyChatPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (chat
,
1608 EMPATHY_TYPE_CHAT
, EmpathyChatPriv
);
1611 priv
->log_manager
= empathy_log_manager_dup_singleton ();
1612 priv
->contacts_width
= -1;
1613 priv
->sent_messages
= NULL
;
1614 priv
->sent_messages_index
= -1;
1615 priv
->account_manager
= empathy_account_manager_dup_singleton ();
1617 g_signal_connect (priv
->account_manager
,
1619 G_CALLBACK (chat_new_connection_cb
),
1622 /* Block events for some time to avoid having "has come online" or
1623 * "joined" messages. */
1624 priv
->block_events_timeout_id
=
1625 g_timeout_add_seconds (1, chat_block_events_timeout_cb
, chat
);
1627 /* Add nick name completion */
1628 priv
->completion
= g_completion_new ((GCompletionFunc
) empathy_contact_get_name
);
1629 g_completion_set_compare (priv
->completion
, chat_contacts_completion_func
);
1633 empathy_chat_new (EmpathyTpChat
*tp_chat
)
1635 return g_object_new (EMPATHY_TYPE_CHAT
, "tp-chat", tp_chat
, NULL
);
1639 empathy_chat_get_tp_chat (EmpathyChat
*chat
)
1641 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1643 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1645 return priv
->tp_chat
;
1649 empathy_chat_set_tp_chat (EmpathyChat
*chat
,
1650 EmpathyTpChat
*tp_chat
)
1652 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1653 TpConnection
*connection
;
1655 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1656 g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat
));
1657 g_return_if_fail (empathy_tp_chat_is_ready (tp_chat
));
1659 if (priv
->tp_chat
) {
1663 if (priv
->account
) {
1664 g_object_unref (priv
->account
);
1667 priv
->tp_chat
= g_object_ref (tp_chat
);
1668 connection
= empathy_tp_chat_get_connection (priv
->tp_chat
);
1669 priv
->account
= empathy_account_manager_get_account (priv
->account_manager
,
1671 g_object_ref (priv
->account
);
1673 g_signal_connect (tp_chat
, "destroy",
1674 G_CALLBACK (chat_destroy_cb
),
1676 g_signal_connect (tp_chat
, "message-received",
1677 G_CALLBACK (chat_message_received_cb
),
1679 g_signal_connect (tp_chat
, "send-error",
1680 G_CALLBACK (chat_send_error_cb
),
1682 g_signal_connect (tp_chat
, "chat-state-changed",
1683 G_CALLBACK (chat_state_changed_cb
),
1685 g_signal_connect (tp_chat
, "property-changed",
1686 G_CALLBACK (chat_property_changed_cb
),
1688 g_signal_connect (tp_chat
, "members-changed",
1689 G_CALLBACK (chat_members_changed_cb
),
1691 g_signal_connect_swapped (tp_chat
, "notify::remote-contact",
1692 G_CALLBACK (chat_remote_contact_changed_cb
),
1695 chat_remote_contact_changed_cb (chat
);
1697 if (chat
->input_text_view
) {
1698 gtk_widget_set_sensitive (chat
->input_text_view
, TRUE
);
1699 if (priv
->block_events_timeout_id
== 0) {
1700 empathy_chat_view_append_event (chat
->view
, _("Connected"));
1704 g_object_notify (G_OBJECT (chat
), "tp-chat");
1705 g_object_notify (G_OBJECT (chat
), "id");
1706 g_object_notify (G_OBJECT (chat
), "account");
1708 /* This is a noop when tp-chat is set at object construction time and causes
1709 * the pending messages to be show when it's set on the object after it has
1711 show_pending_messages (chat
);
1715 empathy_chat_get_account (EmpathyChat
*chat
)
1717 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1719 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1721 return priv
->account
;
1725 empathy_chat_get_id (EmpathyChat
*chat
)
1727 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1729 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1735 empathy_chat_get_name (EmpathyChat
*chat
)
1737 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1740 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1743 if (!ret
&& priv
->remote_contact
) {
1744 ret
= empathy_contact_get_name (priv
->remote_contact
);
1750 return ret
? ret
: _("Conversation");
1754 empathy_chat_get_subject (EmpathyChat
*chat
)
1756 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1758 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1760 return priv
->subject
;
1764 empathy_chat_get_remote_contact (EmpathyChat
*chat
)
1766 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1768 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1770 return priv
->remote_contact
;
1774 empathy_chat_get_contact_menu (EmpathyChat
*chat
)
1776 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1777 GtkWidget
*menu
= NULL
;
1779 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1781 if (priv
->remote_contact
) {
1782 menu
= empathy_contact_menu_new (priv
->remote_contact
,
1783 EMPATHY_CONTACT_FEATURE_CALL
|
1784 EMPATHY_CONTACT_FEATURE_LOG
|
1785 EMPATHY_CONTACT_FEATURE_INFO
);
1787 else if (priv
->contact_list_view
) {
1788 EmpathyContactListView
*view
;
1790 view
= EMPATHY_CONTACT_LIST_VIEW (priv
->contact_list_view
);
1791 menu
= empathy_contact_list_view_get_contact_menu (view
);
1798 empathy_chat_clear (EmpathyChat
*chat
)
1800 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1802 empathy_chat_view_clear (chat
->view
);
1806 empathy_chat_scroll_down (EmpathyChat
*chat
)
1808 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1810 empathy_chat_view_scroll_down (chat
->view
);
1814 empathy_chat_cut (EmpathyChat
*chat
)
1816 GtkTextBuffer
*buffer
;
1818 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1820 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1821 if (gtk_text_buffer_get_has_selection (buffer
)) {
1822 GtkClipboard
*clipboard
;
1824 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
1826 gtk_text_buffer_cut_clipboard (buffer
, clipboard
, TRUE
);
1831 empathy_chat_copy (EmpathyChat
*chat
)
1833 GtkTextBuffer
*buffer
;
1835 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1837 if (empathy_chat_view_get_has_selection (chat
->view
)) {
1838 empathy_chat_view_copy_clipboard (chat
->view
);
1842 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1843 if (gtk_text_buffer_get_has_selection (buffer
)) {
1844 GtkClipboard
*clipboard
;
1846 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
1848 gtk_text_buffer_copy_clipboard (buffer
, clipboard
);
1853 empathy_chat_paste (EmpathyChat
*chat
)
1855 GtkTextBuffer
*buffer
;
1856 GtkClipboard
*clipboard
;
1858 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1860 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1861 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
1863 gtk_text_buffer_paste_clipboard (buffer
, clipboard
, NULL
, TRUE
);
1867 empathy_chat_correct_word (EmpathyChat
*chat
,
1870 const gchar
*new_word
)
1872 GtkTextBuffer
*buffer
;
1874 g_return_if_fail (chat
!= NULL
);
1875 g_return_if_fail (new_word
!= NULL
);
1877 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1879 gtk_text_buffer_delete (buffer
, start
, end
);
1880 gtk_text_buffer_insert (buffer
, start
,
1886 empathy_chat_is_room (EmpathyChat
*chat
)
1888 EmpathyChatPriv
*priv
= GET_PRIV (chat
);
1890 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), FALSE
);
1892 return (priv
->handle_type
== TP_HANDLE_TYPE_ROOM
);