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;
99 gchar *text; /* Original message that was specified
100 * upon entry creation. */
101 gchar *modified_text; /* Message that was modified by user.
102 * When no modifications were made, it is NULL */
122 static guint signals[LAST_SIGNAL] = { 0 };
124 G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BIN);
127 chat_get_property (GObject *object,
132 EmpathyChat *chat = EMPATHY_CHAT (object);
133 EmpathyChatPriv *priv = GET_PRIV (object);
137 g_value_set_object (value, priv->tp_chat);
140 g_value_set_object (value, priv->account);
143 g_value_set_string (value, empathy_chat_get_name (chat));
146 g_value_set_string (value, priv->id);
149 g_value_set_string (value, priv->subject);
151 case PROP_REMOTE_CONTACT:
152 g_value_set_object (value, priv->remote_contact);
154 case PROP_SHOW_CONTACTS:
155 g_value_set_boolean (value, priv->show_contacts);
158 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
164 chat_set_property (GObject *object,
169 EmpathyChat *chat = EMPATHY_CHAT (object);
173 empathy_chat_set_tp_chat (chat, EMPATHY_TP_CHAT (g_value_get_object (value)));
175 case PROP_SHOW_CONTACTS:
176 empathy_chat_set_show_contacts (chat, g_value_get_boolean (value));
179 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
185 chat_connect_channel_reconnected (EmpathyDispatchOperation *dispatch,
189 EmpathyChat *chat = EMPATHY_CHAT (user_data);
190 EmpathyTpChat *tpchat;
193 empathy_chat_view_append_event (chat->view,
194 _("Failed to reconnect this chat"));
198 tpchat = EMPATHY_TP_CHAT (
199 empathy_dispatch_operation_get_channel_wrapper (dispatch));
201 if (empathy_dispatch_operation_claim (dispatch)) {
202 empathy_chat_set_tp_chat (chat, tpchat);
207 chat_new_connection_cb (TpAccount *account,
211 gchar *dbus_error_name,
215 EmpathyChatPriv *priv = GET_PRIV (chat);
216 TpConnection *connection;
218 connection = tp_account_get_connection (account);
220 if (!priv->tp_chat && account == priv->account &&
221 priv->handle_type != TP_HANDLE_TYPE_NONE &&
222 !EMP_STR_EMPTY (priv->id)) {
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 ();
247 chat_composing_remove_timeout (EmpathyChat *chat)
249 EmpathyChatPriv *priv;
251 priv = GET_PRIV (chat);
253 if (priv->composing_stop_timeout_id) {
254 g_source_remove (priv->composing_stop_timeout_id);
255 priv->composing_stop_timeout_id = 0;
260 chat_composing_stop_timeout_cb (EmpathyChat *chat)
262 EmpathyChatPriv *priv;
264 priv = GET_PRIV (chat);
266 priv->composing_stop_timeout_id = 0;
267 empathy_tp_chat_set_state (priv->tp_chat,
268 TP_CHANNEL_CHAT_STATE_PAUSED);
274 chat_composing_start (EmpathyChat *chat)
276 EmpathyChatPriv *priv;
278 priv = GET_PRIV (chat);
280 if (priv->composing_stop_timeout_id) {
281 /* Just restart the timeout */
282 chat_composing_remove_timeout (chat);
284 empathy_tp_chat_set_state (priv->tp_chat,
285 TP_CHANNEL_CHAT_STATE_COMPOSING);
288 priv->composing_stop_timeout_id = g_timeout_add_seconds (
289 COMPOSING_STOP_TIMEOUT,
290 (GSourceFunc) chat_composing_stop_timeout_cb,
295 chat_composing_stop (EmpathyChat *chat)
297 EmpathyChatPriv *priv;
299 priv = GET_PRIV (chat);
301 chat_composing_remove_timeout (chat);
302 empathy_tp_chat_set_state (priv->tp_chat,
303 TP_CHANNEL_CHAT_STATE_ACTIVE);
307 chat_input_history_entry_cmp (InputHistoryEntry *entry,
310 if (!tp_strdiff (entry->text, text)) {
311 if (entry->modified_text != NULL) {
312 /* Modified entry and single string cannot be equal. */
320 static InputHistoryEntry *
321 chat_input_history_entry_new_with_text (const gchar *text)
323 InputHistoryEntry *entry;
324 entry = g_slice_new0 (InputHistoryEntry);
325 entry->text = g_strdup (text);
331 chat_input_history_entry_free (InputHistoryEntry *entry)
333 g_free (entry->text);
334 g_free (entry->modified_text);
335 g_slice_free (InputHistoryEntry, entry);
339 chat_input_history_entry_revert (InputHistoryEntry *entry)
341 g_free (entry->modified_text);
342 entry->modified_text = NULL;
346 chat_input_history_entry_update_text (InputHistoryEntry *entry,
351 if (!tp_strdiff (text, entry->text)) {
352 g_free (entry->modified_text);
353 entry->modified_text = NULL;
357 old = entry->modified_text;
358 entry->modified_text = g_strdup (text);
363 chat_input_history_entry_get_text (InputHistoryEntry *entry)
369 if (entry->modified_text != NULL) {
370 return entry->modified_text;
376 chat_input_history_remove_item (GList *list,
379 list = g_list_remove_link (list, item);
380 chat_input_history_entry_free (item->data);
381 g_list_free_1 (item);
386 chat_input_history_revert (EmpathyChat *chat)
388 EmpathyChatPriv *priv;
392 InputHistoryEntry *entry;
394 priv = GET_PRIV (chat);
395 list = priv->input_history;
398 DEBUG ("No input history");
402 /* Delete temporary entry */
403 if (priv->input_history_current != NULL) {
405 list = chat_input_history_remove_item (list, item1);
406 if (priv->input_history_current == item1) {
407 /* Removed temporary entry was current entry */
408 priv->input_history = list;
409 priv->input_history_current = NULL;
414 /* There is no entry to revert */
418 /* Restore the current history entry to original value */
419 item1 = priv->input_history_current;
421 chat_input_history_entry_revert (entry);
423 /* Remove restored entry if there is other occurance before this entry */
424 item2 = g_list_find_custom (list, chat_input_history_entry_get_text (entry),
425 (GCompareFunc) chat_input_history_entry_cmp);
426 if (item2 != item1) {
427 list = chat_input_history_remove_item (list, item1);
430 /* Remove other occurance of the restored entry */
431 item2 = g_list_find_custom (item1->next,
432 chat_input_history_entry_get_text (entry),
433 (GCompareFunc) chat_input_history_entry_cmp);
435 list = chat_input_history_remove_item (list, item2);
439 priv->input_history_current = NULL;
440 priv->input_history = list;
444 chat_input_history_add (EmpathyChat *chat,
448 EmpathyChatPriv *priv;
451 InputHistoryEntry *entry;
453 priv = GET_PRIV (chat);
455 list = priv->input_history;
457 /* Remove any other occurances of this entry, if not temporary */
459 while ((item = g_list_find_custom (list, str,
460 (GCompareFunc) chat_input_history_entry_cmp)) != NULL) {
461 list = chat_input_history_remove_item (list, item);
464 /* Trim the list to the last 10 items */
465 while (g_list_length (list) > 10) {
466 item = g_list_last (list);
468 list = chat_input_history_remove_item (list, item);
476 entry = chat_input_history_entry_new_with_text (str);
477 list = g_list_prepend (list, entry);
479 /* Set the list and the current item pointer */
480 priv->input_history = list;
482 priv->input_history_current = list;
485 priv->input_history_current = NULL;
490 chat_input_history_get_next (EmpathyChat *chat)
492 EmpathyChatPriv *priv;
496 priv = GET_PRIV (chat);
498 if (priv->input_history == NULL) {
499 DEBUG ("No input history, next entry is NULL");
502 g_assert (priv->input_history_current != NULL);
504 if ((item = g_list_next (priv->input_history_current)) == NULL)
506 item = priv->input_history_current;
509 msg = chat_input_history_entry_get_text (item->data);
511 DEBUG ("Returning next entry: '%s'", msg);
513 priv->input_history_current = item;
519 chat_input_history_get_prev (EmpathyChat *chat)
521 EmpathyChatPriv *priv;
525 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
527 priv = GET_PRIV (chat);
529 if (priv->input_history == NULL) {
530 DEBUG ("No input history, previous entry is NULL");
534 if (priv->input_history_current == NULL)
538 else if ((item = g_list_previous (priv->input_history_current)) == NULL)
540 item = priv->input_history_current;
543 msg = chat_input_history_entry_get_text (item->data);
545 DEBUG ("Returning previous entry: '%s'", msg);
547 priv->input_history_current = item;
553 chat_input_history_update (EmpathyChat *chat,
554 GtkTextBuffer *buffer)
556 EmpathyChatPriv *priv;
557 GtkTextIter start, end;
559 InputHistoryEntry *entry;
561 priv = GET_PRIV (chat);
563 gtk_text_buffer_get_bounds (buffer, &start, &end);
564 text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
566 if (priv->input_history_current == NULL) {
567 /* Add the current text temporarily to the history */
568 chat_input_history_add (chat, text, TRUE);
573 /* Save the changes in the history */
574 entry = priv->input_history_current->data;
575 if (tp_strdiff (chat_input_history_entry_get_text (entry), text)) {
576 chat_input_history_entry_update_text (entry, text);
583 chat_command_join_cb (EmpathyDispatchOperation *dispatch,
587 EmpathyChat *chat = user_data;
590 DEBUG ("Error: %s", error->message);
591 empathy_chat_view_append_event (chat->view,
592 _("Failed to join chatroom"));
599 } ChatCommandMsgData;
602 chat_command_msg_cb (EmpathyDispatchOperation *dispatch,
606 ChatCommandMsgData *data = user_data;
609 empathy_chat_view_append_event (data->chat->view,
610 _("Failed to open private chat"));
614 if (!EMP_STR_EMPTY (data->message)) {
615 EmpathyTpChat *tpchat;
616 EmpathyMessage *message;
618 tpchat = EMPATHY_TP_CHAT (
619 empathy_dispatch_operation_get_channel_wrapper (dispatch));
621 message = empathy_message_new (data->message);
622 empathy_tp_chat_send (tpchat, message);
623 g_object_unref (message);
627 g_free (data->message);
628 g_slice_free (ChatCommandMsgData, data);
632 chat_command_clear (EmpathyChat *chat,
635 empathy_chat_view_clear (chat->view);
639 chat_command_topic (EmpathyChat *chat,
642 EmpathyChatPriv *priv = GET_PRIV (chat);
643 EmpathyTpChatProperty *property;
644 GValue value = {0, };
646 property = empathy_tp_chat_get_property (priv->tp_chat, "subject");
647 if (property == NULL) {
648 empathy_chat_view_append_event (chat->view,
649 _("Topic not supported on this conversation"));
653 if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
654 empathy_chat_view_append_event (chat->view,
655 _("You are not allowed to change the topic"));
659 g_value_init (&value, G_TYPE_STRING);
660 g_value_set_string (&value, strv[1]);
661 empathy_tp_chat_set_property (priv->tp_chat, "subject", &value);
662 g_value_unset (&value);
666 chat_command_join (EmpathyChat *chat,
669 EmpathyChatPriv *priv = GET_PRIV (chat);
670 TpConnection *connection;
672 connection = empathy_tp_chat_get_connection (priv->tp_chat);
673 empathy_dispatcher_join_muc (connection, strv[1],
674 chat_command_join_cb,
679 chat_command_msg_internal (EmpathyChat *chat,
680 const gchar *contact_id,
681 const gchar *message)
683 EmpathyChatPriv *priv = GET_PRIV (chat);
684 TpConnection *connection;
685 ChatCommandMsgData *data;
687 /* FIXME: We should probably search in members alias. But this
688 * is enough for IRC */
689 data = g_slice_new (ChatCommandMsgData);
691 data->message = g_strdup (message);
692 connection = empathy_tp_chat_get_connection (priv->tp_chat);
693 empathy_dispatcher_chat_with_contact_id (connection, contact_id,
699 chat_command_query (EmpathyChat *chat,
702 /* If <message> part is not defined,
703 * strv[2] will be the terminal NULL */
704 chat_command_msg_internal (chat, strv[1], strv[2]);
708 chat_command_msg (EmpathyChat *chat,
711 chat_command_msg_internal (chat, strv[1], strv[2]);
715 chat_command_me (EmpathyChat *chat,
718 EmpathyChatPriv *priv = GET_PRIV (chat);
719 EmpathyMessage *message;
721 message = empathy_message_new (strv[1]);
722 empathy_message_set_tptype (message, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
723 empathy_tp_chat_send (priv->tp_chat, message);
724 g_object_unref (message);
728 chat_command_say (EmpathyChat *chat,
731 EmpathyChatPriv *priv = GET_PRIV (chat);
732 EmpathyMessage *message;
734 message = empathy_message_new (strv[1]);
735 empathy_tp_chat_send (priv->tp_chat, message);
736 g_object_unref (message);
739 static void chat_command_help (EmpathyChat *chat, GStrv strv);
741 typedef void (*ChatCommandFunc) (EmpathyChat *chat, GStrv strv);
747 ChatCommandFunc func;
751 static ChatCommandItem commands[] = {
752 {"clear", 1, 1, chat_command_clear,
753 N_("/clear, clear all messages from the current conversation")},
755 {"topic", 2, 2, chat_command_topic,
756 N_("/topic <topic>, set the topic of the current conversation")},
758 {"join", 2, 2, chat_command_join,
759 N_("/join <chatroom id>, join a new chatroom")},
761 {"j", 2, 2, chat_command_join,
762 N_("/j <chatroom id>, join a new chatroom")},
764 {"query", 2, 3, chat_command_query,
765 N_("/query <contact id> [<message>], open a private chat")},
767 {"msg", 3, 3, chat_command_msg,
768 N_("/msg <contact id> <message>, open a private chat")},
770 {"me", 2, 2, chat_command_me,
771 N_("/me <message>, send an ACTION message to the current conversation")},
773 {"say", 2, 2, chat_command_say,
774 N_("/say <message>, send <message> to the current conversation. "
775 "This is used to send a message starting with a '/'. For example: "
776 "\"/say /join is used to join a new chatroom\"")},
778 {"help", 1, 2, chat_command_help,
779 N_("/help [<command>], show all supported commands. "
780 "If <command> is defined, show its usage.")},
784 chat_command_show_help (EmpathyChat *chat,
785 ChatCommandItem *item)
789 str = g_strdup_printf (_("Usage: %s"), _(item->help));
790 empathy_chat_view_append_event (chat->view, str);
795 chat_command_help (EmpathyChat *chat,
800 /* If <command> part is not defined,
801 * strv[1] will be the terminal NULL */
802 if (strv[1] == NULL) {
803 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
804 empathy_chat_view_append_event (chat->view,
805 _(commands[i].help));
810 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
811 if (g_ascii_strcasecmp (strv[1], commands[i].prefix) == 0) {
812 chat_command_show_help (chat, &commands[i]);
817 empathy_chat_view_append_event (chat->view,
818 _("Unknown command"));
822 chat_command_parse (const gchar *text, guint max_parts)
827 DEBUG ("Parse command, parts=%d text=\"%s\":", max_parts, text);
829 array = g_ptr_array_sized_new (max_parts + 1);
830 while (max_parts > 1) {
833 /* Skip white spaces */
834 while (g_ascii_isspace (*text)) {
838 /* Search the end of this part, until first space. */
839 for (end = text; *end != '\0' && !g_ascii_isspace (*end); end++)
845 item = g_strndup (text, end - text);
846 g_ptr_array_add (array, item);
847 DEBUG ("\tITEM: \"%s\"", item);
853 /* Append last part if not empty */
854 item = g_strstrip (g_strdup (text));
855 if (!EMP_STR_EMPTY (item)) {
856 g_ptr_array_add (array, item);
857 DEBUG ("\tITEM: \"%s\"", item);
862 /* Make the array NULL-terminated */
863 g_ptr_array_add (array, NULL);
865 return (GStrv) g_ptr_array_free (array, FALSE);
869 has_prefix_case (const gchar *s,
872 return g_ascii_strncasecmp (s, prefix, strlen (prefix)) == 0;
876 chat_send (EmpathyChat *chat,
879 EmpathyChatPriv *priv;
880 EmpathyMessage *message;
883 if (EMP_STR_EMPTY (msg)) {
887 priv = GET_PRIV (chat);
889 chat_input_history_add (chat, msg, FALSE);
892 gboolean second_slash = FALSE;
893 const gchar *iter = msg + 1;
895 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
900 if (!has_prefix_case (msg + 1, commands[i].prefix)) {
903 c = *(msg + 1 + strlen (commands[i].prefix));
904 if (c != '\0' && !g_ascii_isspace (c)) {
908 /* We can't use g_strsplit here because it does
909 * not deal correctly if we have more than one space
911 strv = chat_command_parse (msg + 1, commands[i].max_parts);
913 strv_len = g_strv_length (strv);
914 if (strv_len < commands[i].min_parts ||
915 strv_len > commands[i].max_parts) {
916 chat_command_show_help (chat, &commands[i]);
921 commands[i].func (chat, strv);
926 /* Also allow messages with two slashes before the
927 * first space, so it is possible to send a /unix/path.
928 * This heuristic is kind of crap. */
929 while (*iter != '\0' && !g_ascii_isspace (*iter)) {
938 empathy_chat_view_append_event (chat->view,
939 _("Unknown command, see /help for the available"
945 message = empathy_message_new (msg);
946 empathy_tp_chat_send (priv->tp_chat, message);
947 g_object_unref (message);
951 chat_input_text_view_send (EmpathyChat *chat)
953 EmpathyChatPriv *priv;
954 GtkTextBuffer *buffer;
955 GtkTextIter start, end;
958 priv = GET_PRIV (chat);
960 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
962 gtk_text_buffer_get_bounds (buffer, &start, &end);
963 msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
965 /* clear the input field */
966 gtk_text_buffer_set_text (buffer, "", -1);
967 /* delete input history modifications */
968 chat_input_history_revert (chat);
970 chat_send (chat, msg);
975 chat_state_changed_cb (EmpathyTpChat *tp_chat,
976 EmpathyContact *contact,
977 TpChannelChatState state,
980 EmpathyChatPriv *priv;
982 gboolean was_composing;
984 priv = GET_PRIV (chat);
986 if (empathy_contact_is_user (contact)) {
987 /* We don't care about our own chat state */
991 was_composing = (priv->compositors != NULL);
993 /* Find the contact in the list. After that l is the list elem or NULL */
994 for (l = priv->compositors; l; l = l->next) {
995 if (contact == l->data) {
1001 case TP_CHANNEL_CHAT_STATE_GONE:
1002 case TP_CHANNEL_CHAT_STATE_INACTIVE:
1003 case TP_CHANNEL_CHAT_STATE_PAUSED:
1004 case TP_CHANNEL_CHAT_STATE_ACTIVE:
1005 /* Contact is not composing */
1007 priv->compositors = g_list_remove_link (priv->compositors, l);
1008 g_object_unref (l->data);
1012 case TP_CHANNEL_CHAT_STATE_COMPOSING:
1013 /* Contact is composing */
1015 priv->compositors = g_list_prepend (priv->compositors,
1016 g_object_ref (contact));
1020 g_assert_not_reached ();
1023 DEBUG ("Was composing: %s now composing: %s",
1024 was_composing ? "yes" : "no",
1025 priv->compositors ? "yes" : "no");
1027 if ((was_composing && !priv->compositors) ||
1028 (!was_composing && priv->compositors)) {
1029 /* Composing state changed */
1030 g_signal_emit (chat, signals[COMPOSING], 0,
1031 priv->compositors != NULL);
1036 chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
1038 EmpathyChatPriv *priv = GET_PRIV (chat);
1039 EmpathyContact *sender;
1041 sender = empathy_message_get_sender (message);
1043 DEBUG ("Appending new message from %s (%d)",
1044 empathy_contact_get_name (sender),
1045 empathy_contact_get_handle (sender));
1047 empathy_chat_view_append_message (chat->view, message);
1049 /* We received a message so the contact is no longer composing */
1050 chat_state_changed_cb (priv->tp_chat, sender,
1051 TP_CHANNEL_CHAT_STATE_ACTIVE,
1054 g_signal_emit (chat, signals[NEW_MESSAGE], 0, message);
1058 chat_message_received_cb (EmpathyTpChat *tp_chat,
1059 EmpathyMessage *message,
1062 chat_message_received (chat, message);
1063 empathy_tp_chat_acknowledge_message (tp_chat, message);
1067 chat_send_error_cb (EmpathyTpChat *tp_chat,
1068 const gchar *message_body,
1069 TpChannelTextSendError error_code,
1075 switch (error_code) {
1076 case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
1077 error = _("offline");
1079 case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
1080 error = _("invalid contact");
1082 case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
1083 error = _("permission denied");
1085 case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
1086 error = _("too long message");
1088 case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
1089 error = _("not implemented");
1092 error = _("unknown");
1096 str = g_strdup_printf (_("Error sending message '%s': %s"),
1099 empathy_chat_view_append_event (chat->view, str);
1104 chat_property_changed_cb (EmpathyTpChat *tp_chat,
1109 EmpathyChatPriv *priv = GET_PRIV (chat);
1111 if (!tp_strdiff (name, "subject")) {
1112 g_free (priv->subject);
1113 priv->subject = g_value_dup_string (value);
1114 g_object_notify (G_OBJECT (chat), "subject");
1116 if (EMP_STR_EMPTY (priv->subject)) {
1117 gtk_widget_hide (priv->hbox_topic);
1119 gtk_label_set_text (GTK_LABEL (priv->label_topic), priv->subject);
1120 gtk_widget_show (priv->hbox_topic);
1122 if (priv->block_events_timeout_id == 0) {
1125 if (!EMP_STR_EMPTY (priv->subject)) {
1126 str = g_strdup_printf (_("Topic set to: %s"), priv->subject);
1128 str = g_strdup (_("No topic defined"));
1130 empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str);
1134 else if (!tp_strdiff (name, "name")) {
1135 g_free (priv->name);
1136 priv->name = g_value_dup_string (value);
1137 g_object_notify (G_OBJECT (chat), "name");
1142 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
1145 EmpathyChatPriv *priv;
1146 GtkTextIter start, end;
1148 gboolean spell_checker = FALSE;
1150 priv = GET_PRIV (chat);
1152 if (gtk_text_buffer_get_char_count (buffer) == 0) {
1153 chat_composing_stop (chat);
1155 chat_composing_start (chat);
1158 empathy_conf_get_bool (empathy_conf_get (),
1159 EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
1162 gtk_text_buffer_get_start_iter (buffer, &start);
1164 if (!spell_checker) {
1165 gtk_text_buffer_get_end_iter (buffer, &end);
1166 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
1170 if (!empathy_spell_supported ()) {
1174 /* NOTE: this is really inefficient, we shouldn't have to
1175 reiterate the whole buffer each time and check each work
1178 gboolean correct = FALSE;
1181 if (gtk_text_iter_is_start (&start)) {
1184 if (!gtk_text_iter_forward_word_end (&end)) {
1185 /* no whole word yet */
1189 if (!gtk_text_iter_forward_word_end (&end)) {
1190 /* must be the end of the buffer */
1195 gtk_text_iter_backward_word_start (&start);
1198 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1200 /* spell check string if not a command */
1201 if (str[0] != '/') {
1202 correct = empathy_spell_check (str);
1208 gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
1210 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
1215 /* set start iter to the end iters position */
1221 chat_input_key_press_event_cb (GtkWidget *widget,
1225 EmpathyChatPriv *priv;
1228 GtkWidget *text_view_sw;
1230 priv = GET_PRIV (chat);
1232 /* Catch ctrl+up/down so we can traverse messages we sent */
1233 if ((event->state & GDK_CONTROL_MASK) &&
1234 (event->keyval == GDK_Up ||
1235 event->keyval == GDK_Down)) {
1236 GtkTextBuffer *buffer;
1239 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1240 chat_input_history_update (chat, buffer);
1242 if (event->keyval == GDK_Up) {
1243 str = chat_input_history_get_next (chat);
1245 str = chat_input_history_get_prev (chat);
1248 g_signal_handlers_block_by_func (buffer,
1249 chat_input_text_buffer_changed_cb,
1251 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
1252 g_signal_handlers_unblock_by_func (buffer,
1253 chat_input_text_buffer_changed_cb,
1259 /* Catch enter but not ctrl/shift-enter */
1260 if (IS_ENTER (event->keyval) &&
1261 !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
1264 /* This is to make sure that kinput2 gets the enter. And if
1265 * it's handled there we shouldn't send on it. This is because
1266 * kinput2 uses Enter to commit letters. See:
1267 * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
1270 view = GTK_TEXT_VIEW (chat->input_text_view);
1271 if (gtk_im_context_filter_keypress (view->im_context, event)) {
1272 GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
1276 chat_input_text_view_send (chat);
1280 text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
1282 if (IS_ENTER (event->keyval) &&
1283 (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
1284 /* Newline for shift/control-enter. */
1287 if (!(event->state & GDK_CONTROL_MASK) &&
1288 event->keyval == GDK_Page_Up) {
1289 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
1290 gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) - gtk_adjustment_get_page_size (adj));
1293 if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
1294 event->keyval == GDK_Page_Down) {
1295 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
1296 val = MIN (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj),
1297 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
1298 gtk_adjustment_set_value (adj, val);
1301 if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) &&
1302 event->keyval == GDK_Tab) {
1303 GtkTextBuffer *buffer;
1304 GtkTextIter start, current;
1305 gchar *nick, *completed;
1306 GList *list, *completed_list;
1307 gboolean is_start_of_buffer;
1309 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (EMPATHY_CHAT (chat)->input_text_view));
1310 gtk_text_buffer_get_iter_at_mark (buffer, ¤t, gtk_text_buffer_get_insert (buffer));
1312 /* Get the start of the nick to complete. */
1313 gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
1314 gtk_text_iter_backward_word_start (&start);
1315 is_start_of_buffer = gtk_text_iter_is_start (&start);
1317 list = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (priv->tp_chat));
1318 g_completion_add_items (priv->completion, list);
1320 nick = gtk_text_buffer_get_text (buffer, &start, ¤t, FALSE);
1321 completed_list = g_completion_complete (priv->completion,
1330 gchar *complete_char = NULL;
1332 gtk_text_buffer_delete (buffer, &start, ¤t);
1334 len = g_list_length (completed_list);
1337 /* If we only have one hit, use that text
1338 * instead of the text in completed since the
1339 * completed text will use the typed string
1340 * which might be cased all wrong.
1343 text = empathy_contact_get_name (completed_list->data);
1348 gtk_text_buffer_insert_at_cursor (buffer, text, strlen (text));
1350 if (len == 1 && is_start_of_buffer &&
1351 empathy_conf_get_string (empathy_conf_get (),
1352 EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR,
1354 complete_char != NULL) {
1355 gtk_text_buffer_insert_at_cursor (buffer,
1357 strlen (complete_char));
1358 gtk_text_buffer_insert_at_cursor (buffer, " ", 1);
1359 g_free (complete_char);
1365 g_completion_clear_items (priv->completion);
1367 g_list_foreach (list, (GFunc) g_object_unref, NULL);
1377 chat_text_view_focus_in_event_cb (GtkWidget *widget,
1381 gtk_widget_grab_focus (chat->input_text_view);
1387 chat_input_set_size_request_idle (gpointer sw)
1389 gtk_widget_set_size_request (sw, -1, MAX_INPUT_HEIGHT);
1395 chat_input_size_request_cb (GtkWidget *widget,
1396 GtkRequisition *requisition,
1399 EmpathyChatPriv *priv = GET_PRIV (chat);
1402 sw = gtk_widget_get_parent (widget);
1403 if (requisition->height >= MAX_INPUT_HEIGHT && !priv->has_input_vscroll) {
1404 g_idle_add (chat_input_set_size_request_idle, sw);
1405 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1408 priv->has_input_vscroll = TRUE;
1411 if (requisition->height < MAX_INPUT_HEIGHT && priv->has_input_vscroll) {
1412 gtk_widget_set_size_request (sw, -1, -1);
1413 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1416 priv->has_input_vscroll = FALSE;
1421 chat_input_realize_cb (GtkWidget *widget,
1424 DEBUG ("Setting focus to the input text view");
1425 if (gtk_widget_is_sensitive (widget)) {
1426 gtk_widget_grab_focus (widget);
1431 chat_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1432 EmpathySmiley *smiley,
1435 EmpathyChat *chat = EMPATHY_CHAT (user_data);
1436 GtkTextBuffer *buffer;
1439 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1441 gtk_text_buffer_get_end_iter (buffer, &iter);
1442 gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1444 gtk_text_buffer_get_end_iter (buffer, &iter);
1445 gtk_text_buffer_insert (buffer, &iter, " ", -1);
1456 static EmpathyChatSpell *
1457 chat_spell_new (EmpathyChat *chat,
1462 EmpathyChatSpell *chat_spell;
1464 chat_spell = g_slice_new0 (EmpathyChatSpell);
1466 chat_spell->chat = g_object_ref (chat);
1467 chat_spell->word = g_strdup (word);
1468 chat_spell->start = start;
1469 chat_spell->end = end;
1475 chat_spell_free (EmpathyChatSpell *chat_spell)
1477 g_object_unref (chat_spell->chat);
1478 g_free (chat_spell->word);
1479 g_slice_free (EmpathyChatSpell, chat_spell);
1483 chat_spelling_menu_activate_cb (GtkMenuItem *menu_item,
1484 EmpathyChatSpell *chat_spell)
1486 empathy_chat_correct_word (chat_spell->chat,
1487 &(chat_spell->start),
1489 gtk_menu_item_get_label (menu_item));
1493 chat_spelling_build_menu (EmpathyChatSpell *chat_spell)
1495 GtkWidget *menu, *menu_item;
1496 GList *suggestions, *l;
1498 menu = gtk_menu_new ();
1499 suggestions = empathy_spell_get_suggestions (chat_spell->word);
1500 if (suggestions == NULL) {
1501 menu_item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
1502 gtk_widget_set_sensitive (menu_item, FALSE);
1503 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1505 for (l = suggestions; l; l = l->next) {
1506 menu_item = gtk_menu_item_new_with_label (l->data);
1507 g_signal_connect (G_OBJECT (menu_item),
1509 G_CALLBACK (chat_spelling_menu_activate_cb),
1511 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1514 empathy_spell_free_suggestions (suggestions);
1516 gtk_widget_show_all (menu);
1522 chat_text_send_cb (GtkMenuItem *menuitem,
1525 chat_input_text_view_send (chat);
1529 chat_input_populate_popup_cb (GtkTextView *view,
1533 EmpathyChatPriv *priv;
1534 GtkTextBuffer *buffer;
1535 GtkTextTagTable *table;
1538 GtkTextIter iter, start, end;
1541 EmpathyChatSpell *chat_spell;
1542 GtkWidget *spell_menu;
1543 EmpathySmileyManager *smiley_manager;
1544 GtkWidget *smiley_menu;
1547 priv = GET_PRIV (chat);
1548 buffer = gtk_text_view_get_buffer (view);
1550 /* Add the emoticon menu. */
1551 item = gtk_separator_menu_item_new ();
1552 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1553 gtk_widget_show (item);
1555 item = gtk_image_menu_item_new_with_mnemonic (_("Insert Smiley"));
1556 image = gtk_image_new_from_icon_name ("face-smile",
1557 GTK_ICON_SIZE_MENU);
1558 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1559 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1560 gtk_widget_show (item);
1562 smiley_manager = empathy_smiley_manager_dup_singleton ();
1563 smiley_menu = empathy_smiley_menu_new (smiley_manager,
1564 chat_insert_smiley_activate_cb,
1566 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
1567 g_object_unref (smiley_manager);
1569 /* Add the Send menu item. */
1570 gtk_text_buffer_get_bounds (buffer, &start, &end);
1571 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1572 if (!EMP_STR_EMPTY (str)) {
1573 item = gtk_menu_item_new_with_mnemonic (_("_Send"));
1574 g_signal_connect (G_OBJECT (item), "activate",
1575 G_CALLBACK (chat_text_send_cb), chat);
1576 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1577 gtk_widget_show (item);
1581 /* Add the spell check menu item. */
1582 table = gtk_text_buffer_get_tag_table (buffer);
1583 tag = gtk_text_tag_table_lookup (table, "misspelled");
1584 gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
1585 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
1586 GTK_TEXT_WINDOW_WIDGET,
1589 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
1591 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
1592 gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
1594 str = gtk_text_buffer_get_text (buffer,
1595 &start, &end, FALSE);
1597 if (!EMP_STR_EMPTY (str)) {
1598 chat_spell = chat_spell_new (chat, str, start, end);
1599 g_object_set_data_full (G_OBJECT (menu),
1600 "chat_spell", chat_spell,
1601 (GDestroyNotify) chat_spell_free);
1603 item = gtk_separator_menu_item_new ();
1604 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1605 gtk_widget_show (item);
1607 item = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
1608 image = gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK,
1609 GTK_ICON_SIZE_MENU);
1610 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1612 spell_menu = chat_spelling_build_menu (chat_spell);
1613 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), spell_menu);
1615 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1616 gtk_widget_show (item);
1621 chat_log_filter (EmpathyMessage *message,
1624 EmpathyChat *chat = (EmpathyChat *) user_data;
1625 EmpathyChatPriv *priv = GET_PRIV (chat);
1626 const GList *pending;
1628 pending = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1630 for (; pending; pending = g_list_next (pending)) {
1631 if (empathy_message_equal (message, pending->data)) {
1640 chat_add_logs (EmpathyChat *chat)
1642 EmpathyChatPriv *priv = GET_PRIV (chat);
1643 gboolean is_chatroom;
1644 GList *messages, *l;
1650 /* Turn off scrolling temporarily */
1651 empathy_chat_view_scroll (chat->view, FALSE);
1653 /* Add messages from last conversation */
1654 is_chatroom = priv->handle_type == TP_HANDLE_TYPE_ROOM;
1656 messages = empathy_log_manager_get_filtered_messages (priv->log_manager,
1664 for (l = messages; l; l = g_list_next (l)) {
1665 empathy_chat_view_append_message (chat->view, l->data);
1666 g_object_unref (l->data);
1669 g_list_free (messages);
1671 /* Turn back on scrolling */
1672 empathy_chat_view_scroll (chat->view, TRUE);
1676 chat_contacts_completion_func (const gchar *s1,
1680 gchar *tmp, *nick1, *nick2;
1687 return s1 ? -1 : +1;
1690 tmp = g_utf8_normalize (s1, -1, G_NORMALIZE_DEFAULT);
1691 nick1 = g_utf8_casefold (tmp, -1);
1694 tmp = g_utf8_normalize (s2, -1, G_NORMALIZE_DEFAULT);
1695 nick2 = g_utf8_casefold (tmp, -1);
1698 ret = strncmp (nick1, nick2, n);
1707 build_part_message (guint reason,
1709 EmpathyContact *actor,
1710 const gchar *message)
1712 GString *s = g_string_new ("");
1713 const gchar *actor_name = NULL;
1715 if (actor != NULL) {
1716 actor_name = empathy_contact_get_name (actor);
1719 /* Having an actor only really makes sense for a few actions... */
1721 case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
1722 g_string_append_printf (s, _("%s has disconnected"), name);
1724 case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED:
1725 if (actor_name != NULL) {
1726 /* translators: reverse the order of these arguments
1727 * if the kicked should come before the kicker in your locale.
1729 g_string_append_printf (s, _("%1$s was kicked by %2$s"),
1732 g_string_append_printf (s, _("%s was kicked"), name);
1735 case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
1736 if (actor_name != NULL) {
1737 /* translators: reverse the order of these arguments
1738 * if the banned should come before the banner in your locale.
1740 g_string_append_printf (s, _("%1$s was banned by %2$s"),
1743 g_string_append_printf (s, _("%s was banned"), name);
1747 g_string_append_printf (s, _("%s has left the room"), name);
1750 if (!EMP_STR_EMPTY (message)) {
1751 /* Note to translators: this string is appended to
1752 * notifications like "foo has left the room", with the message
1753 * given by the user living the room. If this poses a problem,
1754 * please let us know. :-)
1756 g_string_append_printf (s, _(" (%s)"), message);
1759 return g_string_free (s, FALSE);
1763 chat_members_changed_cb (EmpathyTpChat *tp_chat,
1764 EmpathyContact *contact,
1765 EmpathyContact *actor,
1771 EmpathyChatPriv *priv = GET_PRIV (chat);
1772 const gchar *name = empathy_contact_get_name (contact);
1775 if (priv->block_events_timeout_id != 0)
1779 str = g_strdup_printf (_("%s has joined the room"),
1782 str = build_part_message (reason, name, actor, message);
1785 empathy_chat_view_append_event (chat->view, str);
1790 chat_reset_size_request (gpointer widget)
1792 gtk_widget_set_size_request (widget, -1, -1);
1798 chat_update_contacts_visibility (EmpathyChat *chat)
1800 EmpathyChatPriv *priv = GET_PRIV (chat);
1803 show = priv->remote_contact == NULL && priv->show_contacts;
1805 if (!priv->scrolled_window_contacts) {
1809 if (show && priv->contact_list_view == NULL) {
1810 EmpathyContactListStore *store;
1813 /* We are adding the contact list to the chat, we don't want the
1814 * chat view to become too small. If the chat view is already
1815 * smaller than 250 make sure that size won't change. If the
1816 * chat view is bigger the contact list will take some space on
1817 * it but we make sure the chat view don't become smaller than
1818 * 250. Relax the size request once the resize is done */
1819 min_width = MIN (priv->vbox_left->allocation.width, 250);
1820 gtk_widget_set_size_request (priv->vbox_left, min_width, -1);
1821 g_idle_add (chat_reset_size_request, priv->vbox_left);
1823 if (priv->contacts_width > 0) {
1824 gtk_paned_set_position (GTK_PANED (priv->hpaned),
1825 priv->contacts_width);
1828 store = empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
1829 priv->contact_list_view = GTK_WIDGET (empathy_contact_list_view_new (store,
1830 EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP,
1831 EMPATHY_CONTACT_FEATURE_CHAT |
1832 EMPATHY_CONTACT_FEATURE_CALL |
1833 EMPATHY_CONTACT_FEATURE_LOG |
1834 EMPATHY_CONTACT_FEATURE_INFO));
1835 gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
1836 priv->contact_list_view);
1837 gtk_widget_show (priv->contact_list_view);
1838 gtk_widget_show (priv->scrolled_window_contacts);
1839 g_object_unref (store);
1841 priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
1842 gtk_widget_hide (priv->scrolled_window_contacts);
1843 if (priv->contact_list_view != NULL) {
1844 gtk_widget_destroy (priv->contact_list_view);
1845 priv->contact_list_view = NULL;
1851 empathy_chat_set_show_contacts (EmpathyChat *chat,
1854 EmpathyChatPriv *priv = GET_PRIV (chat);
1856 priv->show_contacts = show;
1858 chat_update_contacts_visibility (chat);
1860 g_object_notify (G_OBJECT (chat), "show-contacts");
1864 chat_remote_contact_changed_cb (EmpathyChat *chat)
1866 EmpathyChatPriv *priv = GET_PRIV (chat);
1868 if (priv->remote_contact != NULL) {
1869 g_object_unref (priv->remote_contact);
1870 priv->remote_contact = NULL;
1875 priv->id = g_strdup (empathy_tp_chat_get_id (priv->tp_chat));
1876 priv->remote_contact = empathy_tp_chat_get_remote_contact (priv->tp_chat);
1877 if (priv->remote_contact != NULL) {
1878 g_object_ref (priv->remote_contact);
1879 priv->handle_type = TP_HANDLE_TYPE_CONTACT;
1881 else if (priv->tp_chat != NULL) {
1884 channel = empathy_tp_chat_get_channel (priv->tp_chat);
1885 g_object_get (channel, "handle-type", &priv->handle_type, NULL);
1888 chat_update_contacts_visibility (chat);
1890 g_object_notify (G_OBJECT (chat), "remote-contact");
1891 g_object_notify (G_OBJECT (chat), "id");
1895 chat_destroy_cb (EmpathyTpChat *tp_chat,
1898 EmpathyChatPriv *priv;
1900 priv = GET_PRIV (chat);
1902 if (!priv->tp_chat) {
1906 chat_composing_remove_timeout (chat);
1907 g_object_unref (priv->tp_chat);
1908 priv->tp_chat = NULL;
1909 g_object_notify (G_OBJECT (chat), "tp-chat");
1911 empathy_chat_view_append_event (chat->view, _("Disconnected"));
1912 gtk_widget_set_sensitive (chat->input_text_view, FALSE);
1913 empathy_chat_set_show_contacts (chat, FALSE);
1917 show_pending_messages (EmpathyChat *chat) {
1918 EmpathyChatPriv *priv = GET_PRIV (chat);
1919 const GList *messages, *l;
1921 if (chat->view == NULL || priv->tp_chat == NULL)
1924 messages = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1926 for (l = messages; l != NULL ; l = g_list_next (l)) {
1927 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1928 chat_message_received (chat, message);
1930 empathy_tp_chat_acknowledge_messages (priv->tp_chat, messages);
1934 chat_create_ui (EmpathyChat *chat)
1936 EmpathyChatPriv *priv = GET_PRIV (chat);
1940 GtkTextBuffer *buffer;
1942 filename = empathy_file_lookup ("empathy-chat.ui",
1944 gui = empathy_builder_get_file (filename,
1945 "chat_widget", &priv->widget,
1946 "hpaned", &priv->hpaned,
1947 "vbox_left", &priv->vbox_left,
1948 "scrolled_window_chat", &priv->scrolled_window_chat,
1949 "scrolled_window_input", &priv->scrolled_window_input,
1950 "hbox_topic", &priv->hbox_topic,
1951 "label_topic", &priv->label_topic,
1952 "scrolled_window_contacts", &priv->scrolled_window_contacts,
1953 "info_bar_vbox", &priv->info_bar_vbox,
1957 /* Add message view. */
1958 chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
1959 g_signal_connect (chat->view, "focus_in_event",
1960 G_CALLBACK (chat_text_view_focus_in_event_cb),
1962 gtk_container_add (GTK_CONTAINER (priv->scrolled_window_chat),
1963 GTK_WIDGET (chat->view));
1964 gtk_widget_show (GTK_WIDGET (chat->view));
1966 /* Add input GtkTextView */
1967 chat->input_text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
1968 "pixels-above-lines", 2,
1969 "pixels-below-lines", 2,
1970 "pixels-inside-wrap", 1,
1973 "wrap-mode", GTK_WRAP_WORD_CHAR,
1975 g_signal_connect (chat->input_text_view, "key-press-event",
1976 G_CALLBACK (chat_input_key_press_event_cb),
1978 g_signal_connect (chat->input_text_view, "size-request",
1979 G_CALLBACK (chat_input_size_request_cb),
1981 g_signal_connect (chat->input_text_view, "realize",
1982 G_CALLBACK (chat_input_realize_cb),
1984 g_signal_connect (chat->input_text_view, "populate-popup",
1985 G_CALLBACK (chat_input_populate_popup_cb),
1987 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1988 g_signal_connect (buffer, "changed",
1989 G_CALLBACK (chat_input_text_buffer_changed_cb),
1991 gtk_text_buffer_create_tag (buffer, "misspelled",
1992 "underline", PANGO_UNDERLINE_ERROR,
1994 gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
1995 chat->input_text_view);
1996 gtk_widget_show (chat->input_text_view);
1998 /* Initialy hide the topic, will be shown if not empty */
1999 gtk_widget_hide (priv->hbox_topic);
2001 /* Set widget focus order */
2002 list = g_list_append (NULL, priv->scrolled_window_input);
2003 gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
2006 list = g_list_append (NULL, priv->vbox_left);
2007 list = g_list_append (list, priv->scrolled_window_contacts);
2008 gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
2011 list = g_list_append (NULL, priv->hpaned);
2012 list = g_list_append (list, priv->hbox_topic);
2013 gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
2016 /* Add the main widget in the chat widget */
2017 gtk_container_add (GTK_CONTAINER (chat), priv->widget);
2018 g_object_unref (gui);
2022 chat_size_request (GtkWidget *widget,
2023 GtkRequisition *requisition)
2025 GtkBin *bin = GTK_BIN (widget);
2028 requisition->width = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2029 requisition->height = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2031 child = gtk_bin_get_child (bin);
2033 if (child && GTK_WIDGET_VISIBLE (child))
2035 GtkRequisition child_requisition;
2037 gtk_widget_size_request (child, &child_requisition);
2039 requisition->width += child_requisition.width;
2040 requisition->height += child_requisition.height;
2045 chat_size_allocate (GtkWidget *widget,
2046 GtkAllocation *allocation)
2048 GtkBin *bin = GTK_BIN (widget);
2049 GtkAllocation child_allocation;
2052 widget->allocation = *allocation;
2054 child = gtk_bin_get_child (bin);
2056 if (child && GTK_WIDGET_VISIBLE (child))
2058 child_allocation.x = allocation->x + gtk_container_get_border_width (GTK_CONTAINER (widget));
2059 child_allocation.y = allocation->y + gtk_container_get_border_width (GTK_CONTAINER (widget));
2060 child_allocation.width = MAX (allocation->width - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2061 child_allocation.height = MAX (allocation->height - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2063 gtk_widget_size_allocate (child, &child_allocation);
2068 chat_finalize (GObject *object)
2071 EmpathyChatPriv *priv;
2073 chat = EMPATHY_CHAT (object);
2074 priv = GET_PRIV (chat);
2076 DEBUG ("Finalized: %p", object);
2078 g_list_foreach (priv->input_history, (GFunc) chat_input_history_entry_free, NULL);
2079 g_list_free (priv->input_history);
2081 g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
2082 g_list_free (priv->compositors);
2084 chat_composing_remove_timeout (chat);
2086 g_object_unref (priv->account_manager);
2087 g_object_unref (priv->log_manager);
2089 if (priv->tp_chat) {
2090 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2091 chat_destroy_cb, chat);
2092 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2093 chat_message_received_cb, chat);
2094 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2095 chat_send_error_cb, chat);
2096 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2097 chat_state_changed_cb, chat);
2098 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2099 chat_property_changed_cb, chat);
2100 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2101 chat_members_changed_cb, chat);
2102 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2103 chat_remote_contact_changed_cb, chat);
2104 empathy_tp_chat_close (priv->tp_chat);
2105 g_object_unref (priv->tp_chat);
2107 if (priv->account) {
2108 g_object_unref (priv->account);
2110 if (priv->remote_contact) {
2111 g_object_unref (priv->remote_contact);
2114 if (priv->block_events_timeout_id) {
2115 g_source_remove (priv->block_events_timeout_id);
2119 g_free (priv->name);
2120 g_free (priv->subject);
2121 g_completion_free (priv->completion);
2123 G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
2127 chat_constructed (GObject *object)
2129 EmpathyChat *chat = EMPATHY_CHAT (object);
2131 chat_add_logs (chat);
2132 show_pending_messages (chat);
2136 empathy_chat_class_init (EmpathyChatClass *klass)
2138 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2139 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2141 object_class->finalize = chat_finalize;
2142 object_class->get_property = chat_get_property;
2143 object_class->set_property = chat_set_property;
2144 object_class->constructed = chat_constructed;
2146 widget_class->size_request = chat_size_request;
2147 widget_class->size_allocate = chat_size_allocate;
2149 g_object_class_install_property (object_class,
2151 g_param_spec_object ("tp-chat",
2153 "The tp chat object",
2154 EMPATHY_TYPE_TP_CHAT,
2157 G_PARAM_STATIC_STRINGS));
2158 g_object_class_install_property (object_class,
2160 g_param_spec_object ("account",
2161 "Account of the chat",
2162 "The account of the chat",
2165 G_PARAM_STATIC_STRINGS));
2166 g_object_class_install_property (object_class,
2168 g_param_spec_string ("id",
2170 "The id of the chat",
2173 G_PARAM_STATIC_STRINGS));
2174 g_object_class_install_property (object_class,
2176 g_param_spec_string ("name",
2178 "The name of the chat",
2181 G_PARAM_STATIC_STRINGS));
2182 g_object_class_install_property (object_class,
2184 g_param_spec_string ("subject",
2186 "The subject or topic of the chat",
2189 G_PARAM_STATIC_STRINGS));
2190 g_object_class_install_property (object_class,
2191 PROP_REMOTE_CONTACT,
2192 g_param_spec_object ("remote-contact",
2193 "The remote contact",
2194 "The remote contact is any",
2195 EMPATHY_TYPE_CONTACT,
2197 G_PARAM_STATIC_STRINGS));
2198 g_object_class_install_property (object_class,
2200 g_param_spec_boolean ("show-contacts",
2201 "Contacts' visibility",
2202 "The visibility of the contacts' list",
2205 G_PARAM_STATIC_STRINGS));
2207 signals[COMPOSING] =
2208 g_signal_new ("composing",
2209 G_OBJECT_CLASS_TYPE (object_class),
2213 g_cclosure_marshal_VOID__BOOLEAN,
2217 signals[NEW_MESSAGE] =
2218 g_signal_new ("new-message",
2219 G_OBJECT_CLASS_TYPE (object_class),
2223 g_cclosure_marshal_VOID__OBJECT,
2225 1, EMPATHY_TYPE_MESSAGE);
2227 g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
2231 chat_block_events_timeout_cb (gpointer data)
2233 EmpathyChatPriv *priv = GET_PRIV (data);
2235 priv->block_events_timeout_id = 0;
2241 account_manager_prepared_cb (GObject *source_object,
2242 GAsyncResult *result,
2245 GList *accounts, *l;
2246 TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
2247 EmpathyChat *chat = user_data;
2248 GError *error = NULL;
2250 if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
2251 DEBUG ("Failed to prepare the account manager: %s", error->message);
2252 g_error_free (error);
2256 accounts = tp_account_manager_get_valid_accounts (account_manager);
2258 for (l = accounts; l != NULL; l = l->next) {
2259 TpAccount *account = l->data;
2260 empathy_signal_connect_weak (account, "status-changed",
2261 G_CALLBACK (chat_new_connection_cb),
2265 g_list_free (accounts);
2269 empathy_chat_init (EmpathyChat *chat)
2271 EmpathyChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
2272 EMPATHY_TYPE_CHAT, EmpathyChatPriv);
2275 priv->log_manager = empathy_log_manager_dup_singleton ();
2276 priv->contacts_width = -1;
2277 priv->input_history = NULL;
2278 priv->input_history_current = NULL;
2279 priv->account_manager = tp_account_manager_dup ();
2281 tp_account_manager_prepare_async (priv->account_manager, NULL,
2282 account_manager_prepared_cb, chat);
2284 empathy_conf_get_bool (empathy_conf_get (),
2285 EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS,
2286 &priv->show_contacts);
2288 /* Block events for some time to avoid having "has come online" or
2289 * "joined" messages. */
2290 priv->block_events_timeout_id =
2291 g_timeout_add_seconds (1, chat_block_events_timeout_cb, chat);
2293 /* Add nick name completion */
2294 priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_name);
2295 g_completion_set_compare (priv->completion, chat_contacts_completion_func);
2297 chat_create_ui (chat);
2301 empathy_chat_new (EmpathyTpChat *tp_chat)
2303 return g_object_new (EMPATHY_TYPE_CHAT, "tp-chat", tp_chat, NULL);
2307 empathy_chat_get_tp_chat (EmpathyChat *chat)
2309 EmpathyChatPriv *priv = GET_PRIV (chat);
2311 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2313 return priv->tp_chat;
2316 static void display_password_info_bar (EmpathyChat *self,
2320 provide_password_cb (GObject *tp_chat,
2324 EmpathyChat *self = EMPATHY_CHAT (user_data);
2325 EmpathyChatPriv *priv = GET_PRIV (self);
2326 GError *error = NULL;
2328 if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat), res,
2330 DEBUG ("error: %s", error->message);
2331 /* FIXME: what should we do if that's another error? Close the channel?
2332 * Display the raw D-Bus error to the user isn't very useful */
2333 if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED))
2334 display_password_info_bar (self, TRUE);
2335 g_error_free (error);
2340 gtk_widget_set_sensitive (priv->hpaned, TRUE);
2341 gtk_widget_grab_focus (self->input_text_view);
2345 password_infobar_response_cb (GtkWidget *info_bar,
2349 EmpathyChatPriv *priv = GET_PRIV (self);
2351 const gchar *password;
2353 if (response_id != GTK_RESPONSE_OK)
2356 entry = g_object_get_data (G_OBJECT (info_bar), "password-entry");
2357 g_assert (entry != NULL);
2359 password = gtk_entry_get_text (GTK_ENTRY (entry));
2361 empathy_tp_chat_provide_password_async (priv->tp_chat, password,
2362 provide_password_cb, self);
2365 gtk_widget_destroy (info_bar);
2369 password_entry_activate_cb (GtkWidget *entry,
2370 GtkWidget *info_bar)
2372 gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2376 passwd_join_button_cb (GtkButton *button,
2377 GtkWidget *info_bar)
2379 gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2383 display_password_info_bar (EmpathyChat *self,
2386 EmpathyChatPriv *priv = GET_PRIV (self);
2387 GtkWidget *info_bar;
2388 GtkWidget *content_area;
2395 GtkMessageType type;
2396 const gchar *msg, *button_label;
2399 /* Previous password was wrong */
2400 type = GTK_MESSAGE_ERROR;
2401 msg = _("Wrong password; please try again:");
2402 button_label = _("Retry");
2405 /* First time we're trying to join */
2406 type = GTK_MESSAGE_QUESTION;
2407 msg = _("This room is protected by a password:");
2408 button_label = _("Join");
2411 info_bar = gtk_info_bar_new ();
2412 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), type);
2414 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2416 hbox = gtk_hbox_new (FALSE, 3);
2417 gtk_container_add (GTK_CONTAINER (content_area), hbox);
2420 image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
2421 GTK_ICON_SIZE_DIALOG);
2422 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2425 label = gtk_label_new (msg);
2426 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
2428 /* Add password entry */
2429 entry = gtk_entry_new ();
2430 gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
2431 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
2433 g_signal_connect (entry, "activate",
2434 G_CALLBACK (password_entry_activate_cb), info_bar);
2436 /* Focus the password entry once it's realized */
2437 g_signal_connect (entry, "realize", G_CALLBACK (gtk_widget_grab_focus), NULL);
2439 /* Add 'Join' button */
2440 alig = gtk_alignment_new (0, 0.5, 0, 0);
2442 button = gtk_button_new_with_label (button_label);
2443 gtk_container_add (GTK_CONTAINER (alig), button);
2444 gtk_box_pack_start (GTK_BOX (hbox), alig, FALSE, FALSE, 0);
2446 g_signal_connect (button, "clicked", G_CALLBACK (passwd_join_button_cb),
2449 g_object_set_data (G_OBJECT (info_bar), "password-entry", entry);
2451 gtk_box_pack_start (GTK_BOX (priv->info_bar_vbox), info_bar,
2453 gtk_widget_show_all (hbox);
2455 g_signal_connect (info_bar, "response",
2456 G_CALLBACK (password_infobar_response_cb), self);
2458 gtk_widget_show_all (info_bar);
2462 chat_password_needed_changed_cb (EmpathyChat *self)
2464 EmpathyChatPriv *priv = GET_PRIV (self);
2466 if (empathy_tp_chat_password_needed (priv->tp_chat)) {
2467 display_password_info_bar (self, FALSE);
2468 gtk_widget_set_sensitive (priv->hpaned, FALSE);
2473 empathy_chat_set_tp_chat (EmpathyChat *chat,
2474 EmpathyTpChat *tp_chat)
2476 EmpathyChatPriv *priv = GET_PRIV (chat);
2477 TpConnection *connection;
2478 GPtrArray *properties;
2480 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2481 g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
2482 g_return_if_fail (empathy_tp_chat_is_ready (tp_chat));
2484 if (priv->tp_chat) {
2488 if (priv->account) {
2489 g_object_unref (priv->account);
2492 priv->tp_chat = g_object_ref (tp_chat);
2493 connection = empathy_tp_chat_get_connection (priv->tp_chat);
2494 priv->account = g_object_ref (empathy_get_account_for_connection (connection));
2496 g_signal_connect (tp_chat, "destroy",
2497 G_CALLBACK (chat_destroy_cb),
2499 g_signal_connect (tp_chat, "message-received",
2500 G_CALLBACK (chat_message_received_cb),
2502 g_signal_connect (tp_chat, "send-error",
2503 G_CALLBACK (chat_send_error_cb),
2505 g_signal_connect (tp_chat, "chat-state-changed",
2506 G_CALLBACK (chat_state_changed_cb),
2508 g_signal_connect (tp_chat, "property-changed",
2509 G_CALLBACK (chat_property_changed_cb),
2511 g_signal_connect (tp_chat, "members-changed",
2512 G_CALLBACK (chat_members_changed_cb),
2514 g_signal_connect_swapped (tp_chat, "notify::remote-contact",
2515 G_CALLBACK (chat_remote_contact_changed_cb),
2517 g_signal_connect_swapped (tp_chat, "notify::password-needed",
2518 G_CALLBACK (chat_password_needed_changed_cb),
2521 /* Get initial value of properties */
2522 properties = empathy_tp_chat_get_properties (priv->tp_chat);
2523 if (properties != NULL) {
2526 for (i = 0; i < properties->len; i++) {
2527 EmpathyTpChatProperty *property;
2529 property = g_ptr_array_index (properties, i);
2530 if (property->value == NULL)
2533 chat_property_changed_cb (priv->tp_chat,
2540 chat_remote_contact_changed_cb (chat);
2542 if (chat->input_text_view) {
2543 gtk_widget_set_sensitive (chat->input_text_view, TRUE);
2544 if (priv->block_events_timeout_id == 0) {
2545 empathy_chat_view_append_event (chat->view, _("Connected"));
2549 g_object_notify (G_OBJECT (chat), "tp-chat");
2550 g_object_notify (G_OBJECT (chat), "id");
2551 g_object_notify (G_OBJECT (chat), "account");
2553 /* This is a noop when tp-chat is set at object construction time and causes
2554 * the pending messages to be show when it's set on the object after it has
2556 show_pending_messages (chat);
2558 /* check if a password is needed */
2559 chat_password_needed_changed_cb (chat);
2563 empathy_chat_get_account (EmpathyChat *chat)
2565 EmpathyChatPriv *priv = GET_PRIV (chat);
2567 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2569 return priv->account;
2573 empathy_chat_get_id (EmpathyChat *chat)
2575 EmpathyChatPriv *priv = GET_PRIV (chat);
2577 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2583 empathy_chat_get_name (EmpathyChat *chat)
2585 EmpathyChatPriv *priv = GET_PRIV (chat);
2588 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2591 if (!ret && priv->remote_contact) {
2592 ret = empathy_contact_get_name (priv->remote_contact);
2598 return ret ? ret : _("Conversation");
2602 empathy_chat_get_subject (EmpathyChat *chat)
2604 EmpathyChatPriv *priv = GET_PRIV (chat);
2606 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2608 return priv->subject;
2612 empathy_chat_get_remote_contact (EmpathyChat *chat)
2614 EmpathyChatPriv *priv = GET_PRIV (chat);
2616 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2618 return priv->remote_contact;
2622 empathy_chat_get_contact_menu (EmpathyChat *chat)
2624 EmpathyChatPriv *priv = GET_PRIV (chat);
2625 GtkWidget *menu = NULL;
2627 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2629 if (priv->remote_contact) {
2630 menu = empathy_contact_menu_new (priv->remote_contact,
2631 EMPATHY_CONTACT_FEATURE_CALL |
2632 EMPATHY_CONTACT_FEATURE_LOG |
2633 EMPATHY_CONTACT_FEATURE_INFO);
2635 else if (priv->contact_list_view) {
2636 EmpathyContactListView *view;
2638 view = EMPATHY_CONTACT_LIST_VIEW (priv->contact_list_view);
2639 menu = empathy_contact_list_view_get_contact_menu (view);
2646 empathy_chat_clear (EmpathyChat *chat)
2648 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2650 empathy_chat_view_clear (chat->view);
2654 empathy_chat_scroll_down (EmpathyChat *chat)
2656 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2658 empathy_chat_view_scroll_down (chat->view);
2662 empathy_chat_cut (EmpathyChat *chat)
2664 GtkTextBuffer *buffer;
2666 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2668 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2669 if (gtk_text_buffer_get_has_selection (buffer)) {
2670 GtkClipboard *clipboard;
2672 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2674 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
2679 empathy_chat_copy (EmpathyChat *chat)
2681 GtkTextBuffer *buffer;
2683 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2685 if (empathy_chat_view_get_has_selection (chat->view)) {
2686 empathy_chat_view_copy_clipboard (chat->view);
2690 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2691 if (gtk_text_buffer_get_has_selection (buffer)) {
2692 GtkClipboard *clipboard;
2694 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2696 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2701 empathy_chat_paste (EmpathyChat *chat)
2703 GtkTextBuffer *buffer;
2704 GtkClipboard *clipboard;
2705 EmpathyChatPriv *priv;
2707 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2709 priv = GET_PRIV (chat);
2711 if (priv->tp_chat == NULL ||
2712 !GTK_WIDGET_IS_SENSITIVE (chat->input_text_view))
2715 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2716 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2718 gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
2722 empathy_chat_correct_word (EmpathyChat *chat,
2725 const gchar *new_word)
2727 GtkTextBuffer *buffer;
2729 g_return_if_fail (chat != NULL);
2730 g_return_if_fail (new_word != NULL);
2732 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2734 gtk_text_buffer_delete (buffer, start, end);
2735 gtk_text_buffer_insert (buffer, start,
2741 empathy_chat_is_room (EmpathyChat *chat)
2743 EmpathyChatPriv *priv = GET_PRIV (chat);
2745 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
2747 return (priv->handle_type == TP_HANDLE_TYPE_ROOM);