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 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., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, 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.h>
37 #include <libempathy/empathy-contact-manager.h>
38 #include <libempathy/empathy-log-manager.h>
39 #include <libempathy/empathy-debug.h>
40 #include <libempathy/empathy-utils.h>
41 #include <libempathy/empathy-conf.h>
42 #include <libempathy/empathy-marshal.h>
44 #include "empathy-chat.h"
45 #include "empathy-chat-window.h"
46 #include "empathy-geometry.h"
47 #include "empathy-preferences.h"
48 #include "empathy-spell.h"
49 #include "empathy-spell-dialog.h"
50 #include "empathy-ui-utils.h"
52 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_CHAT, EmpathyChatPriv))
54 #define DEBUG_DOMAIN "Chat"
56 #define CHAT_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
57 #define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
59 #define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
61 #define MAX_INPUT_HEIGHT 150
63 #define COMPOSING_STOP_TIMEOUT 5
65 struct _EmpathyChatPriv
{
66 EmpathyContactManager
*manager
;
67 EmpathyLogManager
*log_manager
;
68 EmpathyTpChat
*tp_chat
;
69 EmpathyChatWindow
*window
;
70 guint composing_stop_timeout_id
;
73 GSList
*sent_messages
;
74 gint sent_messages_index
;
77 gboolean first_tp_chat
;
78 time_t last_log_timestamp
;
79 /* Used to automatically shrink a window that has temporarily
80 * grown due to long input.
83 gint default_window_height
;
84 gint last_input_height
;
85 gboolean vscroll_visible
;
96 static void empathy_chat_class_init (EmpathyChatClass
*klass
);
97 static void empathy_chat_init (EmpathyChat
*chat
);
98 static void chat_finalize (GObject
*object
);
99 static void chat_destroy_cb (EmpathyTpChat
*tp_chat
,
101 static void chat_send (EmpathyChat
*chat
,
103 static void chat_input_text_view_send (EmpathyChat
*chat
);
104 static void chat_message_received_cb (EmpathyTpChat
*tp_chat
,
105 EmpathyMessage
*message
,
107 static void chat_send_error_cb (EmpathyTpChat
*tp_chat
,
108 EmpathyMessage
*message
,
109 TelepathyChannelTextSendError error_code
,
111 void chat_sent_message_add (EmpathyChat
*chat
,
113 const gchar
* chat_sent_message_get_next (EmpathyChat
*chat
);
114 const gchar
* chat_sent_message_get_last (EmpathyChat
*chat
);
115 static gboolean
chat_input_key_press_event_cb (GtkWidget
*widget
,
118 static void chat_input_text_buffer_changed_cb (GtkTextBuffer
*buffer
,
120 static gboolean
chat_text_view_focus_in_event_cb (GtkWidget
*widget
,
123 static void chat_text_view_scroll_hide_cb (GtkWidget
*widget
,
125 static void chat_text_view_size_allocate_cb (GtkWidget
*widget
,
126 GtkAllocation
*allocation
,
128 static void chat_text_view_realize_cb (GtkWidget
*widget
,
130 static void chat_text_populate_popup_cb (GtkTextView
*view
,
133 static void chat_text_check_word_spelling_cb (GtkMenuItem
*menuitem
,
134 EmpathyChatSpell
*chat_spell
);
135 static EmpathyChatSpell
*chat_spell_new (EmpathyChat
*chat
,
139 static void chat_spell_free (EmpathyChatSpell
*chat_spell
);
140 static void chat_composing_start (EmpathyChat
*chat
);
141 static void chat_composing_stop (EmpathyChat
*chat
);
142 static void chat_composing_remove_timeout (EmpathyChat
*chat
);
143 static gboolean
chat_composing_stop_timeout_cb (EmpathyChat
*chat
);
144 static void chat_state_changed_cb (EmpathyTpChat
*tp_chat
,
145 EmpathyContact
*contact
,
146 TelepathyChannelChatState state
,
148 static void chat_add_logs (EmpathyChat
*chat
);
149 static gboolean
chat_scroll_down_idle_func (EmpathyChat
*chat
);
159 static guint signals
[LAST_SIGNAL
] = { 0 };
161 G_DEFINE_TYPE (EmpathyChat
, empathy_chat
, G_TYPE_OBJECT
);
164 empathy_chat_class_init (EmpathyChatClass
*klass
)
166 GObjectClass
*object_class
;
168 object_class
= G_OBJECT_CLASS (klass
);
170 object_class
->finalize
= chat_finalize
;
173 g_signal_new ("composing",
174 G_OBJECT_CLASS_TYPE (object_class
),
178 g_cclosure_marshal_VOID__BOOLEAN
,
182 signals
[NEW_MESSAGE
] =
183 g_signal_new ("new-message",
184 G_OBJECT_CLASS_TYPE (object_class
),
188 empathy_marshal_VOID__OBJECT_BOOLEAN
,
190 2, EMPATHY_TYPE_MESSAGE
, G_TYPE_BOOLEAN
);
192 signals
[NAME_CHANGED
] =
193 g_signal_new ("name-changed",
194 G_OBJECT_CLASS_TYPE (object_class
),
198 g_cclosure_marshal_VOID__POINTER
,
202 signals
[STATUS_CHANGED
] =
203 g_signal_new ("status-changed",
204 G_OBJECT_CLASS_TYPE (object_class
),
208 g_cclosure_marshal_VOID__VOID
,
212 g_type_class_add_private (object_class
, sizeof (EmpathyChatPriv
));
216 empathy_chat_init (EmpathyChat
*chat
)
218 EmpathyChatPriv
*priv
;
219 GtkTextBuffer
*buffer
;
221 chat
->view
= empathy_chat_view_new ();
222 chat
->input_text_view
= gtk_text_view_new ();
224 chat
->is_first_char
= TRUE
;
226 g_object_set (chat
->input_text_view
,
227 "pixels-above-lines", 2,
228 "pixels-below-lines", 2,
229 "pixels-inside-wrap", 1,
232 "wrap-mode", GTK_WRAP_WORD_CHAR
,
235 priv
= GET_PRIV (chat
);
237 priv
->manager
= empathy_contact_manager_new ();
238 priv
->log_manager
= empathy_log_manager_new ();
239 priv
->default_window_height
= -1;
240 priv
->vscroll_visible
= FALSE
;
241 priv
->sensitive
= TRUE
;
242 priv
->sent_messages
= NULL
;
243 priv
->sent_messages_index
= -1;
244 priv
->first_tp_chat
= TRUE
;
246 g_signal_connect (chat
->input_text_view
,
248 G_CALLBACK (chat_input_key_press_event_cb
),
251 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
252 g_signal_connect (buffer
,
254 G_CALLBACK (chat_input_text_buffer_changed_cb
),
256 g_signal_connect (chat
->view
,
258 G_CALLBACK (chat_text_view_focus_in_event_cb
),
261 g_signal_connect (chat
->input_text_view
,
263 G_CALLBACK (chat_text_view_size_allocate_cb
),
266 g_signal_connect (chat
->input_text_view
,
268 G_CALLBACK (chat_text_view_realize_cb
),
271 g_signal_connect (GTK_TEXT_VIEW (chat
->input_text_view
),
273 G_CALLBACK (chat_text_populate_popup_cb
),
276 /* create misspelt words identification tag */
277 gtk_text_buffer_create_tag (buffer
,
279 "underline", PANGO_UNDERLINE_ERROR
,
284 chat_finalize (GObject
*object
)
287 EmpathyChatPriv
*priv
;
289 chat
= EMPATHY_CHAT (object
);
290 priv
= GET_PRIV (chat
);
292 empathy_debug (DEBUG_DOMAIN
, "Finalized: %p", object
);
294 g_slist_foreach (priv
->sent_messages
, (GFunc
) g_free
, NULL
);
295 g_slist_free (priv
->sent_messages
);
297 g_list_foreach (priv
->compositors
, (GFunc
) g_object_unref
, NULL
);
298 g_list_free (priv
->compositors
);
300 chat_composing_remove_timeout (chat
);
301 g_object_unref (chat
->account
);
302 g_object_unref (priv
->manager
);
303 g_object_unref (priv
->log_manager
);
306 g_object_unref (priv
->tp_chat
);
309 if (priv
->scroll_idle_id
) {
310 g_source_remove (priv
->scroll_idle_id
);
315 G_OBJECT_CLASS (empathy_chat_parent_class
)->finalize (object
);
319 chat_destroy_cb (EmpathyTpChat
*tp_chat
,
322 EmpathyChatPriv
*priv
;
324 priv
= GET_PRIV (chat
);
327 g_object_unref (priv
->tp_chat
);
328 priv
->tp_chat
= NULL
;
330 priv
->sensitive
= FALSE
;
332 empathy_chat_view_append_event (chat
->view
, _("Disconnected"));
333 gtk_widget_set_sensitive (chat
->input_text_view
, FALSE
);
335 if (EMPATHY_CHAT_GET_CLASS (chat
)->set_tp_chat
) {
336 EMPATHY_CHAT_GET_CLASS (chat
)->set_tp_chat (chat
, NULL
);
341 chat_send (EmpathyChat
*chat
,
344 EmpathyChatPriv
*priv
;
345 EmpathyMessage
*message
;
347 priv
= GET_PRIV (chat
);
349 if (G_STR_EMPTY (msg
)) {
353 chat_sent_message_add (chat
, msg
);
355 if (g_str_has_prefix (msg
, "/clear")) {
356 empathy_chat_view_clear (chat
->view
);
360 /* FIXME: add here something to let group/privrate chat handle
361 * some special messages */
363 message
= empathy_message_new (msg
);
365 empathy_tp_chat_send (priv
->tp_chat
, message
);
367 g_object_unref (message
);
371 chat_input_text_view_send (EmpathyChat
*chat
)
373 EmpathyChatPriv
*priv
;
374 GtkTextBuffer
*buffer
;
375 GtkTextIter start
, end
;
378 priv
= GET_PRIV (chat
);
380 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
382 gtk_text_buffer_get_bounds (buffer
, &start
, &end
);
383 msg
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
385 /* clear the input field */
386 gtk_text_buffer_set_text (buffer
, "", -1);
388 chat_send (chat
, msg
);
392 chat
->is_first_char
= TRUE
;
396 chat_message_received_cb (EmpathyTpChat
*tp_chat
,
397 EmpathyMessage
*message
,
400 EmpathyChatPriv
*priv
;
401 EmpathyContact
*sender
;
404 priv
= GET_PRIV (chat
);
406 timestamp
= empathy_message_get_timestamp (message
);
407 if (timestamp
<= priv
->last_log_timestamp
) {
408 /* Do not take care of messages anterior of the last
409 * logged message. Some Jabber chatroom sends messages
410 * received before we joined the room, this avoid
411 * displaying those messages if we already logged them
412 * last time we joined that room. */
413 empathy_debug (DEBUG_DOMAIN
, "Skipping message because it is "
414 "anterior of last logged message.");
418 sender
= empathy_message_get_sender (message
);
419 empathy_debug (DEBUG_DOMAIN
, "Appending message ('%s')",
420 empathy_contact_get_name (sender
));
422 empathy_log_manager_add_message (priv
->log_manager
,
423 empathy_chat_get_id (chat
),
424 empathy_chat_is_group_chat (chat
),
427 empathy_chat_view_append_message (chat
->view
, message
);
429 if (empathy_chat_should_play_sound (chat
)) {
430 // FIXME: empathy_sound_play (EMPATHY_SOUND_CHAT);
433 /* We received a message so the contact is no more composing */
434 chat_state_changed_cb (tp_chat
, sender
,
435 TP_CHANNEL_CHAT_STATE_ACTIVE
,
438 g_signal_emit (chat
, signals
[NEW_MESSAGE
], 0, message
, FALSE
);
442 chat_send_error_cb (EmpathyTpChat
*tp_chat
,
443 EmpathyMessage
*message
,
444 TelepathyChannelTextSendError error_code
,
450 switch (error_code
) {
451 case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE
:
452 error
= _("offline");
454 case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT
:
455 error
= _("invalid contact");
457 case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED
:
458 error
= _("permission denied");
460 case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG
:
461 error
= _("too long message");
463 case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED
:
464 error
= _("not implemented");
467 error
= _("unknown");
471 str
= g_strdup_printf (_("Error sending message '%s': %s"),
472 empathy_message_get_body (message
),
474 empathy_chat_view_append_event (chat
->view
, str
);
479 chat_sent_message_add (EmpathyChat
*chat
,
482 EmpathyChatPriv
*priv
;
486 priv
= GET_PRIV (chat
);
488 /* Save the sent message in our repeat buffer */
489 list
= priv
->sent_messages
;
491 /* Remove any other occurances of this msg */
492 while ((item
= g_slist_find_custom (list
, str
, (GCompareFunc
) strcmp
)) != NULL
) {
493 list
= g_slist_remove_link (list
, item
);
495 g_slist_free1 (item
);
498 /* Trim the list to the last 10 items */
499 while (g_slist_length (list
) > 10) {
500 item
= g_slist_last (list
);
502 list
= g_slist_remove_link (list
, item
);
504 g_slist_free1 (item
);
508 /* Add new message */
509 list
= g_slist_prepend (list
, g_strdup (str
));
511 /* Set list and reset the index */
512 priv
->sent_messages
= list
;
513 priv
->sent_messages_index
= -1;
517 chat_sent_message_get_next (EmpathyChat
*chat
)
519 EmpathyChatPriv
*priv
;
522 priv
= GET_PRIV (chat
);
524 if (!priv
->sent_messages
) {
525 empathy_debug (DEBUG_DOMAIN
,
526 "No sent messages, next message is NULL");
530 max
= g_slist_length (priv
->sent_messages
) - 1;
532 if (priv
->sent_messages_index
< max
) {
533 priv
->sent_messages_index
++;
536 empathy_debug (DEBUG_DOMAIN
,
537 "Returning next message index:%d",
538 priv
->sent_messages_index
);
540 return g_slist_nth_data (priv
->sent_messages
, priv
->sent_messages_index
);
544 chat_sent_message_get_last (EmpathyChat
*chat
)
546 EmpathyChatPriv
*priv
;
548 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
550 priv
= GET_PRIV (chat
);
552 if (!priv
->sent_messages
) {
553 empathy_debug (DEBUG_DOMAIN
,
554 "No sent messages, last message is NULL");
558 if (priv
->sent_messages_index
>= 0) {
559 priv
->sent_messages_index
--;
562 empathy_debug (DEBUG_DOMAIN
,
563 "Returning last message index:%d",
564 priv
->sent_messages_index
);
566 return g_slist_nth_data (priv
->sent_messages
, priv
->sent_messages_index
);
570 chat_input_key_press_event_cb (GtkWidget
*widget
,
574 EmpathyChatPriv
*priv
;
577 GtkWidget
*text_view_sw
;
579 priv
= GET_PRIV (chat
);
581 if (event
->keyval
== GDK_Tab
&& !(event
->state
& GDK_CONTROL_MASK
)) {
585 /* Catch ctrl+up/down so we can traverse messages we sent */
586 if ((event
->state
& GDK_CONTROL_MASK
) &&
587 (event
->keyval
== GDK_Up
||
588 event
->keyval
== GDK_Down
)) {
589 GtkTextBuffer
*buffer
;
592 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
594 if (event
->keyval
== GDK_Up
) {
595 str
= chat_sent_message_get_next (chat
);
597 str
= chat_sent_message_get_last (chat
);
600 g_signal_handlers_block_by_func (buffer
,
601 chat_input_text_buffer_changed_cb
,
603 gtk_text_buffer_set_text (buffer
, str
? str
: "", -1);
604 g_signal_handlers_unblock_by_func (buffer
,
605 chat_input_text_buffer_changed_cb
,
611 /* Catch enter but not ctrl/shift-enter */
612 if (IS_ENTER (event
->keyval
) && !(event
->state
& (GDK_SHIFT_MASK
| GDK_CONTROL_MASK
))) {
615 /* This is to make sure that kinput2 gets the enter. And if
616 * it's handled there we shouldn't send on it. This is because
617 * kinput2 uses Enter to commit letters. See:
618 * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
621 view
= GTK_TEXT_VIEW (chat
->input_text_view
);
622 if (gtk_im_context_filter_keypress (view
->im_context
, event
)) {
623 GTK_TEXT_VIEW (chat
->input_text_view
)->need_im_reset
= TRUE
;
627 chat_input_text_view_send (chat
);
631 text_view_sw
= gtk_widget_get_parent (GTK_WIDGET (chat
->view
));
633 if (IS_ENTER (event
->keyval
) && (event
->state
& (GDK_SHIFT_MASK
| GDK_CONTROL_MASK
))) {
634 /* Newline for shift-enter. */
637 else if ((event
->state
& GDK_CONTROL_MASK
) != GDK_CONTROL_MASK
&&
638 event
->keyval
== GDK_Page_Up
) {
639 adj
= gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw
));
640 gtk_adjustment_set_value (adj
, adj
->value
- adj
->page_size
);
644 else if ((event
->state
& GDK_CONTROL_MASK
) != GDK_CONTROL_MASK
&&
645 event
->keyval
== GDK_Page_Down
) {
646 adj
= gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw
));
647 val
= MIN (adj
->value
+ adj
->page_size
, adj
->upper
- adj
->page_size
);
648 gtk_adjustment_set_value (adj
, val
);
657 chat_text_view_focus_in_event_cb (GtkWidget
*widget
,
661 gtk_widget_grab_focus (chat
->input_text_view
);
667 chat_input_text_buffer_changed_cb (GtkTextBuffer
*buffer
,
670 EmpathyChatPriv
*priv
;
671 GtkTextIter start
, end
;
673 gboolean spell_checker
= FALSE
;
675 priv
= GET_PRIV (chat
);
677 if (gtk_text_buffer_get_char_count (buffer
) == 0) {
678 chat_composing_stop (chat
);
680 chat_composing_start (chat
);
683 empathy_conf_get_bool (empathy_conf_get (),
684 EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED
,
687 if (chat
->is_first_char
) {
691 GtkAllocation
*allocation
;
693 /* Save the window's size */
694 dialog
= empathy_chat_window_get_dialog (priv
->window
);
695 gtk_window_get_size (GTK_WINDOW (dialog
),
696 NULL
, &window_height
);
698 gtk_widget_size_request (chat
->input_text_view
, &req
);
700 allocation
= >K_WIDGET (chat
->view
)->allocation
;
702 priv
->default_window_height
= window_height
;
703 priv
->last_input_height
= req
.height
;
704 priv
->padding_height
= window_height
- req
.height
- allocation
->height
;
706 chat
->is_first_char
= FALSE
;
709 gtk_text_buffer_get_start_iter (buffer
, &start
);
711 if (!spell_checker
) {
712 gtk_text_buffer_get_end_iter (buffer
, &end
);
713 gtk_text_buffer_remove_tag_by_name (buffer
, "misspelled", &start
, &end
);
717 if (!empathy_spell_supported ()) {
721 /* NOTE: this is really inefficient, we shouldn't have to
722 reiterate the whole buffer each time and check each work
725 gboolean correct
= FALSE
;
728 if (gtk_text_iter_is_start (&start
)) {
731 if (!gtk_text_iter_forward_word_end (&end
)) {
732 /* no whole word yet */
736 if (!gtk_text_iter_forward_word_end (&end
)) {
737 /* must be the end of the buffer */
742 gtk_text_iter_backward_word_start (&start
);
745 str
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
747 /* spell check string */
748 if (!empathy_chat_get_is_command (str
)) {
749 correct
= empathy_spell_check (str
);
755 gtk_text_buffer_apply_tag_by_name (buffer
, "misspelled", &start
, &end
);
757 gtk_text_buffer_remove_tag_by_name (buffer
, "misspelled", &start
, &end
);
762 /* set start iter to the end iters position */
774 chat_change_size_in_idle_cb (ChangeSizeData
*data
)
776 gtk_window_resize (GTK_WINDOW (data
->window
),
777 data
->width
, data
->height
);
783 chat_text_view_scroll_hide_cb (GtkWidget
*widget
,
786 EmpathyChatPriv
*priv
;
789 priv
= GET_PRIV (chat
);
791 priv
->vscroll_visible
= FALSE
;
792 g_signal_handlers_disconnect_by_func (widget
, chat_text_view_scroll_hide_cb
, chat
);
794 sw
= gtk_widget_get_parent (chat
->input_text_view
);
795 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
798 g_object_set (sw
, "height-request", -1, NULL
);
802 chat_text_view_size_allocate_cb (GtkWidget
*widget
,
803 GtkAllocation
*allocation
,
806 EmpathyChatPriv
*priv
;
809 ChangeSizeData
*data
;
812 GtkAllocation
*view_allocation
;
817 priv
= GET_PRIV (chat
);
819 if (priv
->default_window_height
<= 0) {
823 sw
= gtk_widget_get_parent (widget
);
824 if (sw
->allocation
.height
>= MAX_INPUT_HEIGHT
&& !priv
->vscroll_visible
) {
827 priv
->vscroll_visible
= TRUE
;
828 gtk_widget_set_size_request (sw
, sw
->allocation
.width
, MAX_INPUT_HEIGHT
);
829 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
831 GTK_POLICY_AUTOMATIC
);
832 vscroll
= gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (sw
));
833 g_signal_connect (vscroll
, "hide",
834 G_CALLBACK (chat_text_view_scroll_hide_cb
),
838 if (priv
->last_input_height
<= allocation
->height
) {
839 priv
->last_input_height
= allocation
->height
;
843 diff
= priv
->last_input_height
- allocation
->height
;
844 priv
->last_input_height
= allocation
->height
;
846 view_allocation
= >K_WIDGET (chat
->view
)->allocation
;
848 dialog
= empathy_chat_window_get_dialog (priv
->window
);
849 gtk_window_get_size (GTK_WINDOW (dialog
), NULL
, ¤t_height
);
851 new_height
= view_allocation
->height
+ priv
->padding_height
+ allocation
->height
- diff
;
853 if (new_height
<= priv
->default_window_height
) {
854 window_height
= priv
->default_window_height
;
856 window_height
= new_height
;
859 if (current_height
<= window_height
) {
863 /* Restore the window's size */
864 gtk_window_get_size (GTK_WINDOW (dialog
), &width
, NULL
);
866 data
= g_new0 (ChangeSizeData
, 1);
867 data
->window
= dialog
;
869 data
->height
= window_height
;
871 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE
,
872 (GSourceFunc
) chat_change_size_in_idle_cb
,
877 chat_text_view_realize_cb (GtkWidget
*widget
,
880 empathy_debug (DEBUG_DOMAIN
, "Setting focus to the input text view");
881 gtk_widget_grab_focus (widget
);
885 chat_insert_smiley_activate_cb (GtkWidget
*menuitem
,
888 GtkTextBuffer
*buffer
;
892 smiley
= g_object_get_data (G_OBJECT (menuitem
), "smiley_text");
894 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
896 gtk_text_buffer_get_end_iter (buffer
, &iter
);
897 gtk_text_buffer_insert (buffer
, &iter
, smiley
, -1);
899 gtk_text_buffer_get_end_iter (buffer
, &iter
);
900 gtk_text_buffer_insert (buffer
, &iter
, " ", -1);
904 chat_text_populate_popup_cb (GtkTextView
*view
,
908 EmpathyChatPriv
*priv
;
909 GtkTextBuffer
*buffer
;
910 GtkTextTagTable
*table
;
913 GtkTextIter iter
, start
, end
;
916 EmpathyChatSpell
*chat_spell
;
917 GtkWidget
*smiley_menu
;
919 priv
= GET_PRIV (chat
);
921 /* Add the emoticon menu. */
922 item
= gtk_separator_menu_item_new ();
923 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
924 gtk_widget_show (item
);
926 item
= gtk_menu_item_new_with_mnemonic (_("Insert Smiley"));
927 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
928 gtk_widget_show (item
);
930 smiley_menu
= empathy_chat_view_get_smiley_menu (
931 G_CALLBACK (chat_insert_smiley_activate_cb
),
933 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item
), smiley_menu
);
935 /* Add the spell check menu item. */
936 buffer
= gtk_text_view_get_buffer (view
);
937 table
= gtk_text_buffer_get_tag_table (buffer
);
939 tag
= gtk_text_tag_table_lookup (table
, "misspelled");
941 gtk_widget_get_pointer (GTK_WIDGET (view
), &x
, &y
);
943 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view
),
944 GTK_TEXT_WINDOW_WIDGET
,
948 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view
), &iter
, x
, y
);
952 if (gtk_text_iter_backward_to_tag_toggle (&start
, tag
) &&
953 gtk_text_iter_forward_to_tag_toggle (&end
, tag
)) {
955 str
= gtk_text_buffer_get_text (buffer
,
956 &start
, &end
, FALSE
);
959 if (G_STR_EMPTY (str
)) {
963 chat_spell
= chat_spell_new (chat
, str
, start
, end
);
965 g_object_set_data_full (G_OBJECT (menu
),
966 "chat_spell", chat_spell
,
967 (GDestroyNotify
) chat_spell_free
);
969 item
= gtk_separator_menu_item_new ();
970 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
971 gtk_widget_show (item
);
973 item
= gtk_menu_item_new_with_mnemonic (_("_Check Word Spelling..."));
974 g_signal_connect (item
,
976 G_CALLBACK (chat_text_check_word_spelling_cb
),
978 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
979 gtk_widget_show (item
);
983 chat_text_check_word_spelling_cb (GtkMenuItem
*menuitem
,
984 EmpathyChatSpell
*chat_spell
)
986 empathy_spell_dialog_show (chat_spell
->chat
,
992 static EmpathyChatSpell
*
993 chat_spell_new (EmpathyChat
*chat
,
998 EmpathyChatSpell
*chat_spell
;
1000 chat_spell
= g_new0 (EmpathyChatSpell
, 1);
1002 chat_spell
->chat
= g_object_ref (chat
);
1003 chat_spell
->word
= g_strdup (word
);
1004 chat_spell
->start
= start
;
1005 chat_spell
->end
= end
;
1011 chat_spell_free (EmpathyChatSpell
*chat_spell
)
1013 g_object_unref (chat_spell
->chat
);
1014 g_free (chat_spell
->word
);
1015 g_free (chat_spell
);
1019 chat_composing_start (EmpathyChat
*chat
)
1021 EmpathyChatPriv
*priv
;
1023 priv
= GET_PRIV (chat
);
1025 if (priv
->composing_stop_timeout_id
) {
1026 /* Just restart the timeout */
1027 chat_composing_remove_timeout (chat
);
1029 empathy_tp_chat_set_state (priv
->tp_chat
,
1030 TP_CHANNEL_CHAT_STATE_COMPOSING
);
1033 priv
->composing_stop_timeout_id
= g_timeout_add_seconds (
1034 COMPOSING_STOP_TIMEOUT
,
1035 (GSourceFunc
) chat_composing_stop_timeout_cb
,
1040 chat_composing_stop (EmpathyChat
*chat
)
1042 EmpathyChatPriv
*priv
;
1044 priv
= GET_PRIV (chat
);
1046 chat_composing_remove_timeout (chat
);
1047 empathy_tp_chat_set_state (priv
->tp_chat
,
1048 TP_CHANNEL_CHAT_STATE_ACTIVE
);
1052 chat_composing_remove_timeout (EmpathyChat
*chat
)
1054 EmpathyChatPriv
*priv
;
1056 priv
= GET_PRIV (chat
);
1058 if (priv
->composing_stop_timeout_id
) {
1059 g_source_remove (priv
->composing_stop_timeout_id
);
1060 priv
->composing_stop_timeout_id
= 0;
1065 chat_composing_stop_timeout_cb (EmpathyChat
*chat
)
1067 EmpathyChatPriv
*priv
;
1069 priv
= GET_PRIV (chat
);
1071 priv
->composing_stop_timeout_id
= 0;
1072 empathy_tp_chat_set_state (priv
->tp_chat
,
1073 TP_CHANNEL_CHAT_STATE_PAUSED
);
1079 chat_state_changed_cb (EmpathyTpChat
*tp_chat
,
1080 EmpathyContact
*contact
,
1081 TelepathyChannelChatState state
,
1084 EmpathyChatPriv
*priv
;
1086 gboolean was_composing
;
1088 priv
= GET_PRIV (chat
);
1090 if (empathy_contact_is_user (contact
)) {
1091 /* We don't care about our own chat state */
1095 was_composing
= (priv
->compositors
!= NULL
);
1097 /* Find the contact in the list. After that l is the list elem or NULL */
1098 for (l
= priv
->compositors
; l
; l
= l
->next
) {
1099 if (empathy_contact_equal (contact
, l
->data
)) {
1105 case TP_CHANNEL_CHAT_STATE_GONE
:
1106 case TP_CHANNEL_CHAT_STATE_INACTIVE
:
1107 case TP_CHANNEL_CHAT_STATE_PAUSED
:
1108 case TP_CHANNEL_CHAT_STATE_ACTIVE
:
1109 /* Contact is not composing */
1111 priv
->compositors
= g_list_remove_link (priv
->compositors
, l
);
1112 g_object_unref (l
->data
);
1116 case TP_CHANNEL_CHAT_STATE_COMPOSING
:
1117 /* Contact is composing */
1119 priv
->compositors
= g_list_prepend (priv
->compositors
,
1120 g_object_ref (contact
));
1124 g_assert_not_reached ();
1127 empathy_debug (DEBUG_DOMAIN
, "Was composing: %s now composing: %s",
1128 was_composing
? "yes" : "no",
1129 priv
->compositors
? "yes" : "no");
1131 if ((was_composing
&& !priv
->compositors
) ||
1132 (!was_composing
&& priv
->compositors
)) {
1133 /* Composing state changed */
1134 g_signal_emit (chat
, signals
[COMPOSING
], 0,
1135 priv
->compositors
!= NULL
);
1140 chat_add_logs (EmpathyChat
*chat
)
1142 EmpathyChatPriv
*priv
;
1143 GList
*messages
, *l
;
1147 priv
= GET_PRIV (chat
);
1149 /* Turn off scrolling temporarily */
1150 empathy_chat_view_scroll (chat
->view
, FALSE
);
1152 /* Add messages from last conversation */
1153 messages
= empathy_log_manager_get_last_messages (priv
->log_manager
,
1155 empathy_chat_get_id (chat
),
1156 empathy_chat_is_group_chat (chat
));
1157 num_messages
= g_list_length (messages
);
1159 for (l
= messages
, i
= 0; l
; l
= l
->next
, i
++) {
1160 EmpathyMessage
*message
;
1164 /* Only add 10 last messages */
1165 if (num_messages
- i
> 10) {
1166 g_object_unref (message
);
1170 priv
->last_log_timestamp
= empathy_message_get_timestamp (message
);
1171 empathy_chat_view_append_message (chat
->view
, message
);
1173 g_object_unref (message
);
1175 g_list_free (messages
);
1177 /* Turn back on scrolling */
1178 empathy_chat_view_scroll (chat
->view
, TRUE
);
1180 /* Scroll to the most recent messages, we reference the chat
1181 * for the duration of the scroll func.
1183 priv
->scroll_idle_id
= g_idle_add ((GSourceFunc
) chat_scroll_down_idle_func
,
1184 g_object_ref (chat
));
1187 /* Scroll down after the back-log has been received. */
1189 chat_scroll_down_idle_func (EmpathyChat
*chat
)
1191 EmpathyChatPriv
*priv
;
1193 priv
= GET_PRIV (chat
);
1195 empathy_chat_scroll_down (chat
);
1196 g_object_unref (chat
);
1198 priv
->scroll_idle_id
= 0;
1204 empathy_chat_get_is_command (const gchar
*str
)
1206 g_return_val_if_fail (str
!= NULL
, FALSE
);
1208 if (str
[0] != '/') {
1212 if (g_str_has_prefix (str
, "/me")) {
1215 else if (g_str_has_prefix (str
, "/nick")) {
1218 else if (g_str_has_prefix (str
, "/topic")) {
1226 empathy_chat_correct_word (EmpathyChat
*chat
,
1229 const gchar
*new_word
)
1231 GtkTextBuffer
*buffer
;
1233 g_return_if_fail (chat
!= NULL
);
1234 g_return_if_fail (new_word
!= NULL
);
1236 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1238 gtk_text_buffer_delete (buffer
, &start
, &end
);
1239 gtk_text_buffer_insert (buffer
, &start
,
1245 empathy_chat_get_name (EmpathyChat
*chat
)
1247 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1249 if (EMPATHY_CHAT_GET_CLASS (chat
)->get_name
) {
1250 return EMPATHY_CHAT_GET_CLASS (chat
)->get_name (chat
);
1257 empathy_chat_get_tooltip (EmpathyChat
*chat
)
1259 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1261 if (EMPATHY_CHAT_GET_CLASS (chat
)->get_tooltip
) {
1262 return EMPATHY_CHAT_GET_CLASS (chat
)->get_tooltip (chat
);
1269 empathy_chat_get_status_icon_name (EmpathyChat
*chat
)
1271 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1273 if (EMPATHY_CHAT_GET_CLASS (chat
)->get_status_icon_name
) {
1274 return EMPATHY_CHAT_GET_CLASS (chat
)->get_status_icon_name (chat
);
1281 empathy_chat_get_widget (EmpathyChat
*chat
)
1283 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), NULL
);
1285 if (EMPATHY_CHAT_GET_CLASS (chat
)->get_widget
) {
1286 return EMPATHY_CHAT_GET_CLASS (chat
)->get_widget (chat
);
1293 empathy_chat_is_group_chat (EmpathyChat
*chat
)
1295 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), FALSE
);
1297 if (EMPATHY_CHAT_GET_CLASS (chat
)->is_group_chat
) {
1298 return EMPATHY_CHAT_GET_CLASS (chat
)->is_group_chat (chat
);
1305 empathy_chat_is_connected (EmpathyChat
*chat
)
1307 EmpathyChatPriv
*priv
;
1309 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), FALSE
);
1311 priv
= GET_PRIV (chat
);
1313 return (priv
->tp_chat
!= NULL
);
1317 empathy_chat_save_geometry (EmpathyChat
*chat
,
1323 empathy_geometry_save (empathy_chat_get_id (chat
), x
, y
, w
, h
);
1327 empathy_chat_load_geometry (EmpathyChat
*chat
,
1333 empathy_geometry_load (empathy_chat_get_id (chat
), x
, y
, w
, h
);
1337 empathy_chat_set_tp_chat (EmpathyChat
*chat
,
1338 EmpathyTpChat
*tp_chat
)
1340 EmpathyChatPriv
*priv
;
1341 GList
*messages
, *l
;
1343 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1344 g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat
));
1346 priv
= GET_PRIV (chat
);
1348 if (tp_chat
== priv
->tp_chat
) {
1352 if (priv
->tp_chat
) {
1353 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1354 chat_message_received_cb
,
1356 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1359 g_signal_handlers_disconnect_by_func (priv
->tp_chat
,
1362 g_object_unref (priv
->tp_chat
);
1366 priv
->tp_chat
= g_object_ref (tp_chat
);
1367 priv
->id
= g_strdup (empathy_tp_chat_get_id (tp_chat
));
1369 if (priv
->first_tp_chat
) {
1370 chat_add_logs (chat
);
1371 priv
->first_tp_chat
= FALSE
;
1374 g_signal_connect (tp_chat
, "message-received",
1375 G_CALLBACK (chat_message_received_cb
),
1377 g_signal_connect (tp_chat
, "send-error",
1378 G_CALLBACK (chat_send_error_cb
),
1380 g_signal_connect (tp_chat
, "chat-state-changed",
1381 G_CALLBACK (chat_state_changed_cb
),
1383 g_signal_connect (tp_chat
, "destroy",
1384 G_CALLBACK (chat_destroy_cb
),
1387 /* Get pending messages */
1388 empathy_tp_chat_set_acknowledge (tp_chat
, TRUE
);
1389 messages
= empathy_tp_chat_get_pendings (tp_chat
);
1390 for (l
= messages
; l
; l
= l
->next
) {
1391 chat_message_received_cb (tp_chat
, l
->data
, chat
);
1392 g_object_unref (l
->data
);
1394 g_list_free (messages
);
1396 if (!priv
->sensitive
) {
1397 gtk_widget_set_sensitive (chat
->input_text_view
, TRUE
);
1398 empathy_chat_view_append_event (chat
->view
, _("Connected"));
1399 priv
->sensitive
= TRUE
;
1402 if (EMPATHY_CHAT_GET_CLASS (chat
)->set_tp_chat
) {
1403 EMPATHY_CHAT_GET_CLASS (chat
)->set_tp_chat (chat
, tp_chat
);
1409 empathy_chat_get_id (EmpathyChat
*chat
)
1411 EmpathyChatPriv
*priv
;
1413 priv
= GET_PRIV (chat
);
1419 empathy_chat_clear (EmpathyChat
*chat
)
1421 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1423 empathy_chat_view_clear (chat
->view
);
1427 empathy_chat_set_window (EmpathyChat
*chat
,
1428 EmpathyChatWindow
*window
)
1430 EmpathyChatPriv
*priv
;
1432 priv
= GET_PRIV (chat
);
1433 priv
->window
= window
;
1437 empathy_chat_get_window (EmpathyChat
*chat
)
1439 EmpathyChatPriv
*priv
;
1441 priv
= GET_PRIV (chat
);
1443 return priv
->window
;
1447 empathy_chat_scroll_down (EmpathyChat
*chat
)
1449 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1451 empathy_chat_view_scroll_down (chat
->view
);
1455 empathy_chat_cut (EmpathyChat
*chat
)
1457 GtkTextBuffer
*buffer
;
1459 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1461 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1462 if (gtk_text_buffer_get_selection_bounds (buffer
, NULL
, NULL
)) {
1463 GtkClipboard
*clipboard
;
1465 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
1467 gtk_text_buffer_cut_clipboard (buffer
, clipboard
, TRUE
);
1472 empathy_chat_copy (EmpathyChat
*chat
)
1474 GtkTextBuffer
*buffer
;
1476 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1478 if (empathy_chat_view_get_selection_bounds (chat
->view
, NULL
, NULL
)) {
1479 empathy_chat_view_copy_clipboard (chat
->view
);
1483 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1484 if (gtk_text_buffer_get_selection_bounds (buffer
, NULL
, NULL
)) {
1485 GtkClipboard
*clipboard
;
1487 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
1489 gtk_text_buffer_copy_clipboard (buffer
, clipboard
);
1494 empathy_chat_paste (EmpathyChat
*chat
)
1496 GtkTextBuffer
*buffer
;
1497 GtkClipboard
*clipboard
;
1499 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1501 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat
->input_text_view
));
1502 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
1504 gtk_text_buffer_paste_clipboard (buffer
, clipboard
, NULL
, TRUE
);
1508 empathy_chat_present (EmpathyChat
*chat
)
1510 EmpathyChatPriv
*priv
;
1512 g_return_if_fail (EMPATHY_IS_CHAT (chat
));
1514 priv
= GET_PRIV (chat
);
1516 if (priv
->window
== NULL
) {
1517 EmpathyChatWindow
*window
;
1519 window
= empathy_chat_window_get_default ();
1521 window
= empathy_chat_window_new ();
1524 empathy_chat_window_add_chat (window
, chat
);
1527 empathy_chat_window_switch_to_chat (priv
->window
, chat
);
1528 empathy_window_present (
1529 GTK_WINDOW (empathy_chat_window_get_dialog (priv
->window
)),
1532 gtk_widget_grab_focus (chat
->input_text_view
);
1536 empathy_chat_should_play_sound (EmpathyChat
*chat
)
1538 EmpathyChatWindow
*window
;
1539 gboolean play
= TRUE
;
1541 g_return_val_if_fail (EMPATHY_IS_CHAT (chat
), FALSE
);
1543 window
= empathy_chat_get_window (chat
);
1548 play
= !empathy_chat_window_has_focus (window
);
1554 empathy_chat_should_highlight_nick (EmpathyMessage
*message
)
1556 EmpathyContact
*contact
;
1557 const gchar
*msg
, *to
;
1558 gchar
*cf_msg
, *cf_to
;
1562 g_return_val_if_fail (EMPATHY_IS_MESSAGE (message
), FALSE
);
1564 empathy_debug (DEBUG_DOMAIN
, "Highlighting nickname");
1568 msg
= empathy_message_get_body (message
);
1573 contact
= empathy_message_get_receiver (message
);
1574 if (!contact
|| !empathy_contact_is_user (contact
)) {
1578 to
= empathy_contact_get_name (contact
);
1583 cf_msg
= g_utf8_casefold (msg
, -1);
1584 cf_to
= g_utf8_casefold (to
, -1);
1586 ch
= strstr (cf_msg
, cf_to
);
1592 /* Not first in the message */
1593 if ((*(ch
- 1) != ' ') &&
1594 (*(ch
- 1) != ',') &&
1595 (*(ch
- 1) != '.')) {
1600 ch
= ch
+ strlen (cf_to
);
1601 if (ch
>= cf_msg
+ strlen (cf_msg
)) {