]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat.c
account-chooser.c: add a 'ready' signal
[empathy.git] / libempathy-gtk / empathy-chat.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2002-2007 Imendio AB
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
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.
10  *
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.
15  *
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
20  *
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>
26  */
27
28 #include <config.h>
29
30 #include <string.h>
31 #include <stdlib.h>
32
33 #include <gdk/gdkkeysyms.h>
34 #include <glib/gi18n-lib.h>
35 #include <gtk/gtk.h>
36
37 #include <telepathy-glib/account-manager.h>
38 #include <telepathy-glib/util.h>
39
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>
44
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"
54
55 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
56 #include <libempathy/empathy-debug.h>
57
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
63
64 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChat)
65 typedef struct {
66         EmpathyTpChat     *tp_chat;
67         TpAccount         *account;
68         gchar             *id;
69         gchar             *name;
70         gchar             *subject;
71         EmpathyContact    *remote_contact;
72         gboolean           show_contacts;
73
74         EmpathyLogManager *log_manager;
75         TpAccountManager  *account_manager;
76         GList             *input_history;
77         GList             *input_history_current;
78         GList             *compositors;
79         GCompletion       *completion;
80         guint              composing_stop_timeout_id;
81         guint              block_events_timeout_id;
82         TpHandleType       handle_type;
83         gint               contacts_width;
84         gboolean           has_input_vscroll;
85
86         GtkWidget         *widget;
87         GtkWidget         *hpaned;
88         GtkWidget         *vbox_left;
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;
96 } EmpathyChatPriv;
97
98 typedef struct {
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 */
103 } InputHistoryEntry;
104
105 enum {
106         COMPOSING,
107         NEW_MESSAGE,
108         LAST_SIGNAL
109 };
110
111 enum {
112         PROP_0,
113         PROP_TP_CHAT,
114         PROP_ACCOUNT,
115         PROP_ID,
116         PROP_NAME,
117         PROP_SUBJECT,
118         PROP_REMOTE_CONTACT,
119         PROP_SHOW_CONTACTS,
120 };
121
122 static guint signals[LAST_SIGNAL] = { 0 };
123
124 G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BIN);
125
126 static void
127 chat_get_property (GObject    *object,
128                    guint       param_id,
129                    GValue     *value,
130                    GParamSpec *pspec)
131 {
132         EmpathyChat *chat = EMPATHY_CHAT (object);
133         EmpathyChatPriv *priv = GET_PRIV (object);
134
135         switch (param_id) {
136         case PROP_TP_CHAT:
137                 g_value_set_object (value, priv->tp_chat);
138                 break;
139         case PROP_ACCOUNT:
140                 g_value_set_object (value, priv->account);
141                 break;
142         case PROP_NAME:
143                 g_value_set_string (value, empathy_chat_get_name (chat));
144                 break;
145         case PROP_ID:
146                 g_value_set_string (value, priv->id);
147                 break;
148         case PROP_SUBJECT:
149                 g_value_set_string (value, priv->subject);
150                 break;
151         case PROP_REMOTE_CONTACT:
152                 g_value_set_object (value, priv->remote_contact);
153                 break;
154         case PROP_SHOW_CONTACTS:
155                 g_value_set_boolean (value, priv->show_contacts);
156                 break;
157         default:
158                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
159                 break;
160         };
161 }
162
163 static void
164 chat_set_property (GObject      *object,
165                    guint         param_id,
166                    const GValue *value,
167                    GParamSpec   *pspec)
168 {
169         EmpathyChat *chat = EMPATHY_CHAT (object);
170
171         switch (param_id) {
172         case PROP_TP_CHAT:
173                 empathy_chat_set_tp_chat (chat, EMPATHY_TP_CHAT (g_value_get_object (value)));
174                 break;
175         case PROP_SHOW_CONTACTS:
176                 empathy_chat_set_show_contacts (chat, g_value_get_boolean (value));
177                 break;
178         default:
179                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
180                 break;
181         };
182 }
183
184 static void
185 chat_connect_channel_reconnected (EmpathyDispatchOperation *dispatch,
186                                   const GError             *error,
187                                   gpointer                  user_data)
188 {
189         EmpathyChat *chat = EMPATHY_CHAT (user_data);
190         EmpathyTpChat *tpchat;
191
192         if (error != NULL) {
193                 empathy_chat_view_append_event (chat->view,
194                         _("Failed to reconnect this chat"));
195                 return;
196         }
197
198         tpchat = EMPATHY_TP_CHAT (
199                 empathy_dispatch_operation_get_channel_wrapper (dispatch));
200
201         if (empathy_dispatch_operation_claim (dispatch)) {
202                 empathy_chat_set_tp_chat (chat, tpchat);
203         }
204 }
205
206 static void
207 chat_new_connection_cb (TpAccount   *account,
208                         guint        old_status,
209                         guint        new_status,
210                         guint        reason,
211                         gchar       *dbus_error_name,
212                         GHashTable  *details,
213                         EmpathyChat *chat)
214 {
215         EmpathyChatPriv *priv = GET_PRIV (chat);
216         TpConnection *connection;
217
218         connection = tp_account_get_connection (account);
219
220         if (!priv->tp_chat && account == priv->account &&
221             priv->handle_type != TP_HANDLE_TYPE_NONE &&
222             !EMP_STR_EMPTY (priv->id)) {
223
224                 DEBUG ("Account reconnected, request a new Text channel");
225
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,
231                                         chat);
232                                 break;
233                         case TP_HANDLE_TYPE_ROOM:
234                                 empathy_dispatcher_join_muc (connection,
235                                         priv->id,
236                                         chat_connect_channel_reconnected,
237                                         chat);
238                                 break;
239                         default:
240                                 g_assert_not_reached ();
241                                 break;
242                 }
243         }
244 }
245
246 static void
247 chat_composing_remove_timeout (EmpathyChat *chat)
248 {
249         EmpathyChatPriv *priv;
250
251         priv = GET_PRIV (chat);
252
253         if (priv->composing_stop_timeout_id) {
254                 g_source_remove (priv->composing_stop_timeout_id);
255                 priv->composing_stop_timeout_id = 0;
256         }
257 }
258
259 static gboolean
260 chat_composing_stop_timeout_cb (EmpathyChat *chat)
261 {
262         EmpathyChatPriv *priv;
263
264         priv = GET_PRIV (chat);
265
266         priv->composing_stop_timeout_id = 0;
267         empathy_tp_chat_set_state (priv->tp_chat,
268                                    TP_CHANNEL_CHAT_STATE_PAUSED);
269
270         return FALSE;
271 }
272
273 static void
274 chat_composing_start (EmpathyChat *chat)
275 {
276         EmpathyChatPriv *priv;
277
278         priv = GET_PRIV (chat);
279
280         if (priv->composing_stop_timeout_id) {
281                 /* Just restart the timeout */
282                 chat_composing_remove_timeout (chat);
283         } else {
284                 empathy_tp_chat_set_state (priv->tp_chat,
285                                            TP_CHANNEL_CHAT_STATE_COMPOSING);
286         }
287
288         priv->composing_stop_timeout_id = g_timeout_add_seconds (
289                 COMPOSING_STOP_TIMEOUT,
290                 (GSourceFunc) chat_composing_stop_timeout_cb,
291                 chat);
292 }
293
294 static void
295 chat_composing_stop (EmpathyChat *chat)
296 {
297         EmpathyChatPriv *priv;
298
299         priv = GET_PRIV (chat);
300
301         chat_composing_remove_timeout (chat);
302         empathy_tp_chat_set_state (priv->tp_chat,
303                                    TP_CHANNEL_CHAT_STATE_ACTIVE);
304 }
305
306 static gint
307 chat_input_history_entry_cmp (InputHistoryEntry *entry,
308                               const gchar *text)
309 {
310         if (!tp_strdiff (entry->text, text)) {
311                 if (entry->modified_text != NULL) {
312                         /* Modified entry and single string cannot be equal. */
313                         return 1;
314                 }
315                 return 0;
316         }
317         return 1;
318 }
319
320 static InputHistoryEntry *
321 chat_input_history_entry_new_with_text (const gchar *text)
322 {
323         InputHistoryEntry *entry;
324         entry = g_slice_new0 (InputHistoryEntry);
325         entry->text = g_strdup (text);
326
327         return entry;
328 }
329
330 static void
331 chat_input_history_entry_free (InputHistoryEntry *entry)
332 {
333         g_free (entry->text);
334         g_free (entry->modified_text);
335         g_slice_free (InputHistoryEntry, entry);
336 }
337
338 static void
339 chat_input_history_entry_revert (InputHistoryEntry *entry)
340 {
341         g_free (entry->modified_text);
342         entry->modified_text = NULL;
343 }
344
345 static void
346 chat_input_history_entry_update_text (InputHistoryEntry *entry,
347                                       const gchar *text)
348 {
349         gchar *old;
350
351         if (!tp_strdiff (text, entry->text)) {
352                 g_free (entry->modified_text);
353                 entry->modified_text = NULL;
354                 return;
355         }
356
357         old = entry->modified_text;
358         entry->modified_text = g_strdup (text);
359         g_free (old);
360 }
361
362 static const gchar *
363 chat_input_history_entry_get_text (InputHistoryEntry *entry)
364 {
365         if (entry == NULL) {
366                 return NULL;
367         }
368
369         if (entry->modified_text != NULL) {
370                 return entry->modified_text;
371         }
372         return entry->text;
373 }
374
375 static GList *
376 chat_input_history_remove_item (GList *list,
377                                 GList *item)
378 {
379         list = g_list_remove_link (list, item);
380         chat_input_history_entry_free (item->data);
381         g_list_free_1 (item);
382         return list;
383 }
384
385 static void
386 chat_input_history_revert (EmpathyChat *chat)
387 {
388         EmpathyChatPriv   *priv;
389         GList             *list;
390         GList             *item1;
391         GList             *item2;
392         InputHistoryEntry *entry;
393
394         priv = GET_PRIV (chat);
395         list = priv->input_history;
396
397         if (list == NULL) {
398                 DEBUG ("No input history");
399                 return;
400         }
401
402         /* Delete temporary entry */
403         if (priv->input_history_current != NULL) {
404                 item1 = list;
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;
410                         return;
411                 }
412         }
413         else {
414                 /* There is no entry to revert */
415                 return;
416         }
417
418         /* Restore the current history entry to original value */
419         item1 = priv->input_history_current;
420         entry = item1->data;
421         chat_input_history_entry_revert (entry);
422
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);
428         }
429         else {
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);
434                 if (item2 != NULL) {
435                         list = chat_input_history_remove_item (list, item2);
436                 }
437         }
438
439         priv->input_history_current = NULL;
440         priv->input_history = list;
441 }
442
443 static void
444 chat_input_history_add (EmpathyChat  *chat,
445                         const gchar *str,
446                         gboolean temporary)
447 {
448         EmpathyChatPriv   *priv;
449         GList             *list;
450         GList             *item;
451         InputHistoryEntry *entry;
452
453         priv = GET_PRIV (chat);
454
455         list = priv->input_history;
456
457         /* Remove any other occurances of this entry, if not temporary */
458         if (!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);
462                 }
463
464                 /* Trim the list to the last 10 items */
465                 while (g_list_length (list) > 10) {
466                         item = g_list_last (list);
467                         if (item != NULL) {
468                                 list = chat_input_history_remove_item (list, item);
469                         }
470                 }
471         }
472
473
474
475         /* Add new entry */
476         entry = chat_input_history_entry_new_with_text (str);
477         list = g_list_prepend (list, entry);
478
479         /* Set the list and the current item pointer */
480         priv->input_history = list;
481         if (temporary) {
482                 priv->input_history_current = list;
483         }
484         else {
485                 priv->input_history_current = NULL;
486         }
487 }
488
489 static const gchar *
490 chat_input_history_get_next (EmpathyChat *chat)
491 {
492         EmpathyChatPriv *priv;
493         GList           *item;
494         const gchar     *msg;
495
496         priv = GET_PRIV (chat);
497
498         if (priv->input_history == NULL) {
499                 DEBUG ("No input history, next entry is NULL");
500                 return NULL;
501         }
502         g_assert (priv->input_history_current != NULL);
503
504         if ((item = g_list_next (priv->input_history_current)) == NULL)
505         {
506                 item = priv->input_history_current;
507         }
508
509         msg = chat_input_history_entry_get_text (item->data);
510
511         DEBUG ("Returning next entry: '%s'", msg);
512
513         priv->input_history_current = item;
514
515         return msg;
516 }
517
518 static const gchar *
519 chat_input_history_get_prev (EmpathyChat *chat)
520 {
521         EmpathyChatPriv *priv;
522         GList           *item;
523         const gchar     *msg;
524
525         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
526
527         priv = GET_PRIV (chat);
528
529         if (priv->input_history == NULL) {
530                 DEBUG ("No input history, previous entry is NULL");
531                 return NULL;
532         }
533
534         if (priv->input_history_current == NULL)
535         {
536                 return NULL;
537         }
538         else if ((item = g_list_previous (priv->input_history_current)) == NULL)
539         {
540                 item = priv->input_history_current;
541         }
542
543         msg = chat_input_history_entry_get_text (item->data);
544
545         DEBUG ("Returning previous entry: '%s'", msg);
546
547         priv->input_history_current = item;
548
549         return msg;
550 }
551
552 static void
553 chat_input_history_update (EmpathyChat *chat,
554                            GtkTextBuffer *buffer)
555 {
556         EmpathyChatPriv      *priv;
557         GtkTextIter           start, end;
558         gchar                *text;
559         InputHistoryEntry    *entry;
560
561         priv = GET_PRIV (chat);
562
563         gtk_text_buffer_get_bounds (buffer, &start, &end);
564         text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
565
566         if (priv->input_history_current == NULL) {
567                 /* Add the current text temporarily to the history */
568                 chat_input_history_add (chat, text, TRUE);
569                 g_free (text);
570                 return;
571         }
572
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);
577         }
578
579         g_free (text);
580 }
581
582 static void
583 chat_command_join_cb (EmpathyDispatchOperation *dispatch,
584                       const GError             *error,
585                       gpointer                  user_data)
586 {
587         EmpathyChat *chat = user_data;
588
589         if (error != NULL) {
590                 DEBUG ("Error: %s", error->message);
591                 empathy_chat_view_append_event (chat->view,
592                         _("Failed to join chatroom"));
593         }
594 }
595
596 typedef struct {
597         EmpathyChat *chat;
598         gchar *message;
599 } ChatCommandMsgData;
600
601 static void
602 chat_command_msg_cb (EmpathyDispatchOperation *dispatch,
603                               const GError             *error,
604                               gpointer                  user_data)
605 {
606         ChatCommandMsgData *data = user_data;
607
608         if (error != NULL) {
609                 empathy_chat_view_append_event (data->chat->view,
610                         _("Failed to open private chat"));
611                 goto OUT;
612         }
613
614         if (!EMP_STR_EMPTY (data->message)) {
615                 EmpathyTpChat *tpchat;
616                 EmpathyMessage *message;
617
618                 tpchat = EMPATHY_TP_CHAT (
619                         empathy_dispatch_operation_get_channel_wrapper (dispatch));
620
621                 message = empathy_message_new (data->message);
622                 empathy_tp_chat_send (tpchat, message);
623                 g_object_unref (message);
624         }
625
626 OUT:
627         g_free (data->message);
628         g_slice_free (ChatCommandMsgData, data);
629 }
630
631 static void
632 chat_command_clear (EmpathyChat *chat,
633                     GStrv        strv)
634 {
635         empathy_chat_view_clear (chat->view);
636 }
637
638 static void
639 chat_command_topic (EmpathyChat *chat,
640                     GStrv        strv)
641 {
642         EmpathyChatPriv *priv = GET_PRIV (chat);
643         EmpathyTpChatProperty *property;
644         GValue value = {0, };
645
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"));
650                 return;
651         }
652
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"));
656                 return;
657         }
658
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);
663 }
664
665 static void
666 chat_command_join (EmpathyChat *chat,
667                    GStrv        strv)
668 {
669         EmpathyChatPriv *priv = GET_PRIV (chat);
670         TpConnection *connection;
671
672         connection = empathy_tp_chat_get_connection (priv->tp_chat);
673         empathy_dispatcher_join_muc (connection, strv[1],
674                                      chat_command_join_cb,
675                                      chat);
676 }
677
678 static void
679 chat_command_msg_internal (EmpathyChat *chat,
680                            const gchar *contact_id,
681                            const gchar *message)
682 {
683         EmpathyChatPriv *priv = GET_PRIV (chat);
684         TpConnection *connection;
685         ChatCommandMsgData *data;
686
687         /* FIXME: We should probably search in members alias. But this
688          * is enough for IRC */
689         data = g_slice_new (ChatCommandMsgData);
690         data->chat = chat;
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,
694                                                  chat_command_msg_cb,
695                                                  data);
696 }
697
698 static void
699 chat_command_query (EmpathyChat *chat,
700                     GStrv        strv)
701 {
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]);
705 }
706
707 static void
708 chat_command_msg (EmpathyChat *chat,
709                   GStrv        strv)
710 {
711         chat_command_msg_internal (chat, strv[1], strv[2]);
712 }
713
714 static void
715 chat_command_nick (EmpathyChat *chat,
716                    GStrv        strv)
717 {
718         EmpathyChatPriv *priv = GET_PRIV (chat);
719         TpConnection *connection;
720         GHashTable *new_alias;
721         TpHandle handle;
722
723         connection = tp_account_get_connection (priv->account);
724         handle = tp_connection_get_self_handle (connection);
725         new_alias = g_hash_table_new (g_direct_hash, g_direct_equal);
726         g_hash_table_insert (new_alias, GUINT_TO_POINTER (handle), strv[1]);
727
728         tp_cli_connection_interface_aliasing_call_set_aliases (connection, -1,
729                 new_alias, NULL, NULL, NULL, NULL);
730
731         g_hash_table_destroy (new_alias);
732 }
733
734 static void
735 chat_command_me (EmpathyChat *chat,
736                   GStrv        strv)
737 {
738         EmpathyChatPriv *priv = GET_PRIV (chat);
739         EmpathyMessage *message;
740
741         message = empathy_message_new (strv[1]);
742         empathy_message_set_tptype (message, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
743         empathy_tp_chat_send (priv->tp_chat, message);
744         g_object_unref (message);
745 }
746
747 static void
748 chat_command_say (EmpathyChat *chat,
749                   GStrv        strv)
750 {
751         EmpathyChatPriv *priv = GET_PRIV (chat);
752         EmpathyMessage *message;
753
754         message = empathy_message_new (strv[1]);
755         empathy_tp_chat_send (priv->tp_chat, message);
756         g_object_unref (message);
757 }
758
759 static void chat_command_help (EmpathyChat *chat, GStrv strv);
760
761 typedef void (*ChatCommandFunc) (EmpathyChat *chat, GStrv strv);
762
763 typedef struct {
764         const gchar *prefix;
765         guint min_parts;
766         guint max_parts;
767         ChatCommandFunc func;
768         const gchar *help;
769 } ChatCommandItem;
770
771 static ChatCommandItem commands[] = {
772         {"clear", 1, 1, chat_command_clear,
773          N_("/clear, clear all messages from the current conversation")},
774
775         {"topic", 2, 2, chat_command_topic,
776          N_("/topic <topic>, set the topic of the current conversation")},
777
778         {"join", 2, 2, chat_command_join,
779          N_("/join <chatroom id>, join a new chatroom")},
780
781         {"j", 2, 2, chat_command_join,
782          N_("/j <chatroom id>, join a new chatroom")},
783
784         {"query", 2, 3, chat_command_query,
785          N_("/query <contact id> [<message>], open a private chat")},
786
787         {"msg", 3, 3, chat_command_msg,
788          N_("/msg <contact id> <message>, open a private chat")},
789
790         {"nick", 2, 2, chat_command_nick,
791          N_("/nick <nickname>, change your nickname on current server")},
792
793         {"me", 2, 2, chat_command_me,
794          N_("/me <message>, send an ACTION message to the current conversation")},
795
796         {"say", 2, 2, chat_command_say,
797          N_("/say <message>, send <message> to the current conversation. "
798             "This is used to send a message starting with a '/'. For example: "
799             "\"/say /join is used to join a new chatroom\"")},
800
801         {"help", 1, 2, chat_command_help,
802          N_("/help [<command>], show all supported commands. "
803             "If <command> is defined, show its usage.")},
804 };
805
806 static void
807 chat_command_show_help (EmpathyChat     *chat,
808                         ChatCommandItem *item)
809 {
810         gchar *str;
811
812         str = g_strdup_printf (_("Usage: %s"), _(item->help));
813         empathy_chat_view_append_event (chat->view, str);
814         g_free (str);
815 }
816
817 static void
818 chat_command_help (EmpathyChat *chat,
819                    GStrv        strv)
820 {
821         guint i;
822
823         /* If <command> part is not defined,
824          * strv[1] will be the terminal NULL */
825         if (strv[1] == NULL) {
826                 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
827                         empathy_chat_view_append_event (chat->view,
828                                 _(commands[i].help));
829                 }
830                 return;
831         }
832
833         for (i = 0; i < G_N_ELEMENTS (commands); i++) {
834                 if (g_ascii_strcasecmp (strv[1], commands[i].prefix) == 0) {
835                         chat_command_show_help (chat, &commands[i]);
836                         return;
837                 }
838         }
839
840         empathy_chat_view_append_event (chat->view,
841                 _("Unknown command"));
842 }
843
844 static GStrv
845 chat_command_parse (const gchar *text, guint max_parts)
846 {
847         GPtrArray *array;
848         gchar *item;
849
850         DEBUG ("Parse command, parts=%d text=\"%s\":", max_parts, text);
851
852         array = g_ptr_array_sized_new (max_parts + 1);
853         while (max_parts > 1) {
854                 const gchar *end;
855
856                 /* Skip white spaces */
857                 while (g_ascii_isspace (*text)) {
858                         text++;
859                 }
860
861                 /* Search the end of this part, until first space. */
862                 for (end = text; *end != '\0' && !g_ascii_isspace (*end); end++)
863                         /* Do nothing */;
864                 if (*end == '\0') {
865                         break;
866                 }
867
868                 item = g_strndup (text, end - text);
869                 g_ptr_array_add (array, item);
870                 DEBUG ("\tITEM: \"%s\"", item);
871
872                 text = end;
873                 max_parts--;
874         }
875
876         /* Append last part if not empty */
877         item = g_strstrip (g_strdup (text));
878         if (!EMP_STR_EMPTY (item)) {
879                 g_ptr_array_add (array, item);
880                 DEBUG ("\tITEM: \"%s\"", item);
881         } else {
882                 g_free (item);
883         }
884
885         /* Make the array NULL-terminated */
886         g_ptr_array_add (array, NULL);
887
888         return (GStrv) g_ptr_array_free (array, FALSE);
889 }
890
891 static gboolean
892 has_prefix_case (const gchar *s,
893                   const gchar *prefix)
894 {
895         return g_ascii_strncasecmp (s, prefix, strlen (prefix)) == 0;
896 }
897
898 static void
899 chat_send (EmpathyChat  *chat,
900            const gchar *msg)
901 {
902         EmpathyChatPriv *priv;
903         EmpathyMessage  *message;
904         guint            i;
905
906         if (EMP_STR_EMPTY (msg)) {
907                 return;
908         }
909
910         priv = GET_PRIV (chat);
911
912         chat_input_history_add (chat, msg, FALSE);
913
914         if (msg[0] == '/') {
915                 gboolean second_slash = FALSE;
916                 const gchar *iter = msg + 1;
917
918                 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
919                         GStrv strv;
920                         guint strv_len;
921                         gchar c;
922
923                         if (!has_prefix_case (msg + 1, commands[i].prefix)) {
924                                 continue;
925                         }
926                         c = *(msg + 1 + strlen (commands[i].prefix));
927                         if (c != '\0' && !g_ascii_isspace (c)) {
928                                 continue;
929                         }
930
931                         /* We can't use g_strsplit here because it does
932                          * not deal correctly if we have more than one space
933                          * between args */
934                         strv = chat_command_parse (msg + 1, commands[i].max_parts);
935
936                         strv_len = g_strv_length (strv);
937                         if (strv_len < commands[i].min_parts ||
938                             strv_len > commands[i].max_parts) {
939                                 chat_command_show_help (chat, &commands[i]);
940                                 g_strfreev (strv);
941                                 return;
942                         }
943
944                         commands[i].func (chat, strv);
945                         g_strfreev (strv);
946                         return;
947                 }
948
949                 /* Also allow messages with two slashes before the
950                  * first space, so it is possible to send a /unix/path.
951                  * This heuristic is kind of crap. */
952                 while (*iter != '\0' && !g_ascii_isspace (*iter)) {
953                         if (*iter == '/') {
954                                 second_slash = TRUE;
955                                 break;
956                         }
957                         iter++;
958                 }
959
960                 if (!second_slash) {
961                         empathy_chat_view_append_event (chat->view,
962                                 _("Unknown command, see /help for the available"
963                                   " commands"));
964                         return;
965                 }
966         }
967
968         message = empathy_message_new (msg);
969         empathy_tp_chat_send (priv->tp_chat, message);
970         g_object_unref (message);
971 }
972
973 static void
974 chat_input_text_view_send (EmpathyChat *chat)
975 {
976         EmpathyChatPriv *priv;
977         GtkTextBuffer  *buffer;
978         GtkTextIter     start, end;
979         gchar          *msg;
980
981         priv = GET_PRIV (chat);
982
983         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
984
985         gtk_text_buffer_get_bounds (buffer, &start, &end);
986         msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
987
988         /* clear the input field */
989         gtk_text_buffer_set_text (buffer, "", -1);
990         /* delete input history modifications */
991         chat_input_history_revert (chat);
992
993         chat_send (chat, msg);
994         g_free (msg);
995 }
996
997 static void
998 chat_state_changed_cb (EmpathyTpChat      *tp_chat,
999                        EmpathyContact     *contact,
1000                        TpChannelChatState  state,
1001                        EmpathyChat        *chat)
1002 {
1003         EmpathyChatPriv *priv;
1004         GList          *l;
1005         gboolean        was_composing;
1006
1007         priv = GET_PRIV (chat);
1008
1009         if (empathy_contact_is_user (contact)) {
1010                 /* We don't care about our own chat state */
1011                 return;
1012         }
1013
1014         was_composing = (priv->compositors != NULL);
1015
1016         /* Find the contact in the list. After that l is the list elem or NULL */
1017         for (l = priv->compositors; l; l = l->next) {
1018                 if (contact == l->data) {
1019                         break;
1020                 }
1021         }
1022
1023         switch (state) {
1024         case TP_CHANNEL_CHAT_STATE_GONE:
1025         case TP_CHANNEL_CHAT_STATE_INACTIVE:
1026         case TP_CHANNEL_CHAT_STATE_PAUSED:
1027         case TP_CHANNEL_CHAT_STATE_ACTIVE:
1028                 /* Contact is not composing */
1029                 if (l) {
1030                         priv->compositors = g_list_remove_link (priv->compositors, l);
1031                         g_object_unref (l->data);
1032                         g_list_free1 (l);
1033                 }
1034                 break;
1035         case TP_CHANNEL_CHAT_STATE_COMPOSING:
1036                 /* Contact is composing */
1037                 if (!l) {
1038                         priv->compositors = g_list_prepend (priv->compositors,
1039                                                             g_object_ref (contact));
1040                 }
1041                 break;
1042         default:
1043                 g_assert_not_reached ();
1044         }
1045
1046         DEBUG ("Was composing: %s now composing: %s",
1047                 was_composing ? "yes" : "no",
1048                 priv->compositors ? "yes" : "no");
1049
1050         if ((was_composing && !priv->compositors) ||
1051             (!was_composing && priv->compositors)) {
1052                 /* Composing state changed */
1053                 g_signal_emit (chat, signals[COMPOSING], 0,
1054                                priv->compositors != NULL);
1055         }
1056 }
1057
1058 static void
1059 chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
1060 {
1061         EmpathyChatPriv *priv = GET_PRIV (chat);
1062         EmpathyContact  *sender;
1063
1064         sender = empathy_message_get_sender (message);
1065
1066         DEBUG ("Appending new message from %s (%d)",
1067                 empathy_contact_get_name (sender),
1068                 empathy_contact_get_handle (sender));
1069
1070         empathy_chat_view_append_message (chat->view, message);
1071
1072         /* We received a message so the contact is no longer composing */
1073         chat_state_changed_cb (priv->tp_chat, sender,
1074                                TP_CHANNEL_CHAT_STATE_ACTIVE,
1075                                chat);
1076
1077         g_signal_emit (chat, signals[NEW_MESSAGE], 0, message);
1078 }
1079
1080 static void
1081 chat_message_received_cb (EmpathyTpChat  *tp_chat,
1082                           EmpathyMessage *message,
1083                           EmpathyChat    *chat)
1084 {
1085         chat_message_received (chat, message);
1086         empathy_tp_chat_acknowledge_message (tp_chat, message);
1087 }
1088
1089 static void
1090 chat_send_error_cb (EmpathyTpChat          *tp_chat,
1091                     const gchar            *message_body,
1092                     TpChannelTextSendError  error_code,
1093                     EmpathyChat            *chat)
1094 {
1095         const gchar *error;
1096         gchar       *str;
1097
1098         switch (error_code) {
1099         case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
1100                 error = _("offline");
1101                 break;
1102         case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
1103                 error = _("invalid contact");
1104                 break;
1105         case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
1106                 error = _("permission denied");
1107                 break;
1108         case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
1109                 error = _("too long message");
1110                 break;
1111         case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
1112                 error = _("not implemented");
1113                 break;
1114         default:
1115                 error = _("unknown");
1116                 break;
1117         }
1118
1119         str = g_strdup_printf (_("Error sending message '%s': %s"),
1120                                message_body,
1121                                error);
1122         empathy_chat_view_append_event (chat->view, str);
1123         g_free (str);
1124 }
1125
1126 static void
1127 chat_property_changed_cb (EmpathyTpChat *tp_chat,
1128                           const gchar   *name,
1129                           GValue        *value,
1130                           EmpathyChat   *chat)
1131 {
1132         EmpathyChatPriv *priv = GET_PRIV (chat);
1133
1134         if (!tp_strdiff (name, "subject")) {
1135                 g_free (priv->subject);
1136                 priv->subject = g_value_dup_string (value);
1137                 g_object_notify (G_OBJECT (chat), "subject");
1138
1139                 if (EMP_STR_EMPTY (priv->subject)) {
1140                         gtk_widget_hide (priv->hbox_topic);
1141                 } else {
1142                         gtk_label_set_text (GTK_LABEL (priv->label_topic), priv->subject);
1143                         gtk_widget_show (priv->hbox_topic);
1144                 }
1145                 if (priv->block_events_timeout_id == 0) {
1146                         gchar *str;
1147
1148                         if (!EMP_STR_EMPTY (priv->subject)) {
1149                                 str = g_strdup_printf (_("Topic set to: %s"), priv->subject);
1150                         } else {
1151                                 str = g_strdup (_("No topic defined"));
1152                         }
1153                         empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str);
1154                         g_free (str);
1155                 }
1156         }
1157         else if (!tp_strdiff (name, "name")) {
1158                 g_free (priv->name);
1159                 priv->name = g_value_dup_string (value);
1160                 g_object_notify (G_OBJECT (chat), "name");
1161         }
1162 }
1163
1164 static void
1165 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
1166                                    EmpathyChat    *chat)
1167 {
1168         EmpathyChatPriv *priv;
1169         GtkTextIter     start, end;
1170         gchar          *str;
1171         gboolean        spell_checker = FALSE;
1172
1173         priv = GET_PRIV (chat);
1174
1175         if (gtk_text_buffer_get_char_count (buffer) == 0) {
1176                 chat_composing_stop (chat);
1177         } else {
1178                 chat_composing_start (chat);
1179         }
1180
1181         empathy_conf_get_bool (empathy_conf_get (),
1182                            EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
1183                            &spell_checker);
1184
1185         gtk_text_buffer_get_start_iter (buffer, &start);
1186
1187         if (!spell_checker) {
1188                 gtk_text_buffer_get_end_iter (buffer, &end);
1189                 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
1190                 return;
1191         }
1192
1193         if (!empathy_spell_supported ()) {
1194                 return;
1195         }
1196
1197         /* NOTE: this is really inefficient, we shouldn't have to
1198            reiterate the whole buffer each time and check each work
1199            every time. */
1200         while (TRUE) {
1201                 gboolean correct = FALSE;
1202
1203                 /* if at start */
1204                 if (gtk_text_iter_is_start (&start)) {
1205                         end = start;
1206
1207                         if (!gtk_text_iter_forward_word_end (&end)) {
1208                                 /* no whole word yet */
1209                                 break;
1210                         }
1211                 } else {
1212                         if (!gtk_text_iter_forward_word_end (&end)) {
1213                                 /* must be the end of the buffer */
1214                                 break;
1215                         }
1216
1217                         start = end;
1218                         gtk_text_iter_backward_word_start (&start);
1219                 }
1220
1221                 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1222
1223                 /* spell check string if not a command */
1224                 if (str[0] != '/') {
1225                         correct = empathy_spell_check (str);
1226                 } else {
1227                         correct = TRUE;
1228                 }
1229
1230                 if (!correct) {
1231                         gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
1232                 } else {
1233                         gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
1234                 }
1235
1236                 g_free (str);
1237
1238                 /* set start iter to the end iters position */
1239                 start = end;
1240         }
1241 }
1242
1243 static gboolean
1244 chat_input_key_press_event_cb (GtkWidget   *widget,
1245                                GdkEventKey *event,
1246                                EmpathyChat *chat)
1247 {
1248         EmpathyChatPriv *priv;
1249         GtkAdjustment  *adj;
1250         gdouble         val;
1251         GtkWidget      *text_view_sw;
1252
1253         priv = GET_PRIV (chat);
1254
1255         /* Catch ctrl+up/down so we can traverse messages we sent */
1256         if ((event->state & GDK_CONTROL_MASK) &&
1257             (event->keyval == GDK_Up ||
1258              event->keyval == GDK_Down)) {
1259                 GtkTextBuffer *buffer;
1260                 const gchar   *str;
1261
1262                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1263                 chat_input_history_update (chat, buffer);
1264
1265                 if (event->keyval == GDK_Up) {
1266                         str = chat_input_history_get_next (chat);
1267                 } else {
1268                         str = chat_input_history_get_prev (chat);
1269                 }
1270
1271                 g_signal_handlers_block_by_func (buffer,
1272                                                  chat_input_text_buffer_changed_cb,
1273                                                  chat);
1274                 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
1275                 g_signal_handlers_unblock_by_func (buffer,
1276                                                    chat_input_text_buffer_changed_cb,
1277                                                    chat);
1278
1279                 return TRUE;
1280         }
1281
1282         /* Catch enter but not ctrl/shift-enter */
1283         if (IS_ENTER (event->keyval) &&
1284             !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
1285                 GtkTextView *view;
1286
1287                 /* This is to make sure that kinput2 gets the enter. And if
1288                  * it's handled there we shouldn't send on it. This is because
1289                  * kinput2 uses Enter to commit letters. See:
1290                  * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
1291                  */
1292
1293                 view = GTK_TEXT_VIEW (chat->input_text_view);
1294                 if (gtk_im_context_filter_keypress (view->im_context, event)) {
1295                         GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
1296                         return TRUE;
1297                 }
1298
1299                 chat_input_text_view_send (chat);
1300                 return TRUE;
1301         }
1302
1303         text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
1304
1305         if (IS_ENTER (event->keyval) &&
1306             (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
1307                 /* Newline for shift/control-enter. */
1308                 return FALSE;
1309         }
1310         if (!(event->state & GDK_CONTROL_MASK) &&
1311             event->keyval == GDK_Page_Up) {
1312                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
1313                 gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) - gtk_adjustment_get_page_size (adj));
1314                 return TRUE;
1315         }
1316         if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
1317             event->keyval == GDK_Page_Down) {
1318                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
1319                 val = MIN (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj),
1320                            gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
1321                 gtk_adjustment_set_value (adj, val);
1322                 return TRUE;
1323         }
1324         if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) &&
1325             event->keyval == GDK_Tab) {
1326                 GtkTextBuffer *buffer;
1327                 GtkTextIter    start, current;
1328                 gchar         *nick, *completed;
1329                 GList         *list, *completed_list;
1330                 gboolean       is_start_of_buffer;
1331
1332                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (EMPATHY_CHAT (chat)->input_text_view));
1333                 gtk_text_buffer_get_iter_at_mark (buffer, &current, gtk_text_buffer_get_insert (buffer));
1334
1335                 /* Get the start of the nick to complete. */
1336                 gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
1337                 gtk_text_iter_backward_word_start (&start);
1338                 is_start_of_buffer = gtk_text_iter_is_start (&start);
1339
1340                 list = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (priv->tp_chat));
1341                 g_completion_add_items (priv->completion, list);
1342
1343                 nick = gtk_text_buffer_get_text (buffer, &start, &current, FALSE);
1344                 completed_list = g_completion_complete (priv->completion,
1345                                                         nick,
1346                                                         &completed);
1347
1348                 g_free (nick);
1349
1350                 if (completed) {
1351                         guint        len;
1352                         const gchar *text;
1353                         gchar       *complete_char = NULL;
1354
1355                         gtk_text_buffer_delete (buffer, &start, &current);
1356
1357                         len = g_list_length (completed_list);
1358
1359                         if (len == 1) {
1360                                 /* If we only have one hit, use that text
1361                                  * instead of the text in completed since the
1362                                  * completed text will use the typed string
1363                                  * which might be cased all wrong.
1364                                  * Fixes #120876
1365                                  * */
1366                                 text = empathy_contact_get_name (completed_list->data);
1367                         } else {
1368                                 text = completed;
1369                         }
1370
1371                         gtk_text_buffer_insert_at_cursor (buffer, text, strlen (text));
1372
1373                         if (len == 1 && is_start_of_buffer &&
1374                             empathy_conf_get_string (empathy_conf_get (),
1375                                                      EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR,
1376                                                      &complete_char) &&
1377                             complete_char != NULL) {
1378                                 gtk_text_buffer_insert_at_cursor (buffer,
1379                                                                   complete_char,
1380                                                                   strlen (complete_char));
1381                                 gtk_text_buffer_insert_at_cursor (buffer, " ", 1);
1382                                 g_free (complete_char);
1383                         }
1384
1385                         g_free (completed);
1386                 }
1387
1388                 g_completion_clear_items (priv->completion);
1389
1390                 g_list_foreach (list, (GFunc) g_object_unref, NULL);
1391                 g_list_free (list);
1392
1393                 return TRUE;
1394         }
1395
1396         return FALSE;
1397 }
1398
1399 static gboolean
1400 chat_text_view_focus_in_event_cb (GtkWidget  *widget,
1401                                   GdkEvent   *event,
1402                                   EmpathyChat *chat)
1403 {
1404         gtk_widget_grab_focus (chat->input_text_view);
1405
1406         return TRUE;
1407 }
1408
1409 static gboolean
1410 chat_input_set_size_request_idle (gpointer sw)
1411 {
1412         gtk_widget_set_size_request (sw, -1, MAX_INPUT_HEIGHT);
1413
1414         return FALSE;
1415 }
1416
1417 static void
1418 chat_input_size_request_cb (GtkWidget      *widget,
1419                             GtkRequisition *requisition,
1420                             EmpathyChat    *chat)
1421 {
1422         EmpathyChatPriv *priv = GET_PRIV (chat);
1423         GtkWidget       *sw;
1424
1425         sw = gtk_widget_get_parent (widget);
1426         if (requisition->height >= MAX_INPUT_HEIGHT && !priv->has_input_vscroll) {
1427                 g_idle_add (chat_input_set_size_request_idle, sw);
1428                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1429                                                 GTK_POLICY_NEVER,
1430                                                 GTK_POLICY_ALWAYS);
1431                 priv->has_input_vscroll = TRUE;
1432         }
1433
1434         if (requisition->height < MAX_INPUT_HEIGHT && priv->has_input_vscroll) {
1435                 gtk_widget_set_size_request (sw, -1, -1);
1436                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1437                                                 GTK_POLICY_NEVER,
1438                                                 GTK_POLICY_NEVER);
1439                 priv->has_input_vscroll = FALSE;
1440         }
1441 }
1442
1443 static void
1444 chat_input_realize_cb (GtkWidget   *widget,
1445                        EmpathyChat *chat)
1446 {
1447         DEBUG ("Setting focus to the input text view");
1448         if (gtk_widget_is_sensitive (widget)) {
1449                 gtk_widget_grab_focus (widget);
1450         }
1451 }
1452
1453 static void
1454 chat_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1455                                 EmpathySmiley        *smiley,
1456                                 gpointer              user_data)
1457 {
1458         EmpathyChat   *chat = EMPATHY_CHAT (user_data);
1459         GtkTextBuffer *buffer;
1460         GtkTextIter    iter;
1461
1462         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1463
1464         gtk_text_buffer_get_end_iter (buffer, &iter);
1465         gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1466
1467         gtk_text_buffer_get_end_iter (buffer, &iter);
1468         gtk_text_buffer_insert (buffer, &iter, " ", -1);
1469 }
1470
1471 typedef struct {
1472         EmpathyChat  *chat;
1473         gchar       *word;
1474
1475         GtkTextIter  start;
1476         GtkTextIter  end;
1477 } EmpathyChatSpell;
1478
1479 static EmpathyChatSpell *
1480 chat_spell_new (EmpathyChat  *chat,
1481                 const gchar *word,
1482                 GtkTextIter  start,
1483                 GtkTextIter  end)
1484 {
1485         EmpathyChatSpell *chat_spell;
1486
1487         chat_spell = g_slice_new0 (EmpathyChatSpell);
1488
1489         chat_spell->chat = g_object_ref (chat);
1490         chat_spell->word = g_strdup (word);
1491         chat_spell->start = start;
1492         chat_spell->end = end;
1493
1494         return chat_spell;
1495 }
1496
1497 static void
1498 chat_spell_free (EmpathyChatSpell *chat_spell)
1499 {
1500         g_object_unref (chat_spell->chat);
1501         g_free (chat_spell->word);
1502         g_slice_free (EmpathyChatSpell, chat_spell);
1503 }
1504
1505 static void
1506 chat_spelling_menu_activate_cb (GtkMenuItem     *menu_item,
1507                                                 EmpathyChatSpell *chat_spell)
1508 {
1509     empathy_chat_correct_word (chat_spell->chat,
1510                                &(chat_spell->start),
1511                                &(chat_spell->end),
1512                                gtk_menu_item_get_label (menu_item));
1513 }
1514
1515 static GtkWidget *
1516 chat_spelling_build_menu (EmpathyChatSpell *chat_spell)
1517 {
1518     GtkWidget *menu, *menu_item;
1519     GList     *suggestions, *l;
1520
1521     menu = gtk_menu_new ();
1522     suggestions = empathy_spell_get_suggestions (chat_spell->word);
1523     if (suggestions == NULL) {
1524         menu_item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
1525         gtk_widget_set_sensitive (menu_item, FALSE);
1526         gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1527     } else {
1528         for (l = suggestions; l; l = l->next) {
1529             menu_item = gtk_menu_item_new_with_label (l->data);
1530             g_signal_connect (G_OBJECT (menu_item),
1531                           "activate",
1532                           G_CALLBACK (chat_spelling_menu_activate_cb),
1533                           chat_spell);
1534             gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1535         }
1536     }
1537     empathy_spell_free_suggestions (suggestions);
1538
1539     gtk_widget_show_all (menu);
1540
1541     return menu;
1542 }
1543
1544 static void
1545 chat_text_send_cb (GtkMenuItem *menuitem,
1546                    EmpathyChat *chat)
1547 {
1548         chat_input_text_view_send (chat);
1549 }
1550
1551 static void
1552 chat_input_populate_popup_cb (GtkTextView *view,
1553                               GtkMenu     *menu,
1554                               EmpathyChat *chat)
1555 {
1556         EmpathyChatPriv      *priv;
1557         GtkTextBuffer        *buffer;
1558         GtkTextTagTable      *table;
1559         GtkTextTag           *tag;
1560         gint                  x, y;
1561         GtkTextIter           iter, start, end;
1562         GtkWidget            *item;
1563         gchar                *str = NULL;
1564         EmpathyChatSpell     *chat_spell;
1565         GtkWidget            *spell_menu;
1566         EmpathySmileyManager *smiley_manager;
1567         GtkWidget            *smiley_menu;
1568         GtkWidget            *image;
1569
1570         priv = GET_PRIV (chat);
1571         buffer = gtk_text_view_get_buffer (view);
1572
1573         /* Add the emoticon menu. */
1574         item = gtk_separator_menu_item_new ();
1575         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1576         gtk_widget_show (item);
1577
1578         item = gtk_image_menu_item_new_with_mnemonic (_("Insert Smiley"));
1579         image = gtk_image_new_from_icon_name ("face-smile",
1580                                               GTK_ICON_SIZE_MENU);
1581         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1582         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1583         gtk_widget_show (item);
1584
1585         smiley_manager = empathy_smiley_manager_dup_singleton ();
1586         smiley_menu = empathy_smiley_menu_new (smiley_manager,
1587                                                chat_insert_smiley_activate_cb,
1588                                                chat);
1589         gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
1590         g_object_unref (smiley_manager);
1591
1592         /* Add the Send menu item. */
1593         gtk_text_buffer_get_bounds (buffer, &start, &end);
1594         str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1595         if (!EMP_STR_EMPTY (str)) {
1596                 item = gtk_menu_item_new_with_mnemonic (_("_Send"));
1597                 g_signal_connect (G_OBJECT (item), "activate",
1598                                   G_CALLBACK (chat_text_send_cb), chat);
1599                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1600                 gtk_widget_show (item);
1601         }
1602         str = NULL;
1603
1604         /* Add the spell check menu item. */
1605         table = gtk_text_buffer_get_tag_table (buffer);
1606         tag = gtk_text_tag_table_lookup (table, "misspelled");
1607         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
1608         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
1609                                                GTK_TEXT_WINDOW_WIDGET,
1610                                                x, y,
1611                                                &x, &y);
1612         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
1613         start = end = iter;
1614         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
1615             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
1616
1617                 str = gtk_text_buffer_get_text (buffer,
1618                                                 &start, &end, FALSE);
1619         }
1620         if (!EMP_STR_EMPTY (str)) {
1621                 chat_spell = chat_spell_new (chat, str, start, end);
1622                 g_object_set_data_full (G_OBJECT (menu),
1623                                         "chat_spell", chat_spell,
1624                                         (GDestroyNotify) chat_spell_free);
1625
1626                 item = gtk_separator_menu_item_new ();
1627                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1628                 gtk_widget_show (item);
1629
1630                 item = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
1631                 image = gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK,
1632                                                       GTK_ICON_SIZE_MENU);
1633                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1634
1635                 spell_menu = chat_spelling_build_menu (chat_spell);
1636                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), spell_menu);
1637
1638                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1639                 gtk_widget_show (item);
1640         }
1641 }
1642
1643 static gboolean
1644 chat_log_filter (EmpathyMessage *message,
1645                  gpointer user_data)
1646 {
1647         EmpathyChat *chat = (EmpathyChat *) user_data;
1648         EmpathyChatPriv *priv = GET_PRIV (chat);
1649         const GList *pending;
1650
1651         pending = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1652
1653         for (; pending; pending = g_list_next (pending)) {
1654                 if (empathy_message_equal (message, pending->data)) {
1655                         return FALSE;
1656                 }
1657         }
1658
1659         return TRUE;
1660 }
1661
1662 static void
1663 chat_add_logs (EmpathyChat *chat)
1664 {
1665         EmpathyChatPriv *priv = GET_PRIV (chat);
1666         gboolean         is_chatroom;
1667         GList           *messages, *l;
1668
1669         if (!priv->id) {
1670                 return;
1671         }
1672
1673         /* Turn off scrolling temporarily */
1674         empathy_chat_view_scroll (chat->view, FALSE);
1675
1676         /* Add messages from last conversation */
1677         is_chatroom = priv->handle_type == TP_HANDLE_TYPE_ROOM;
1678
1679         messages = empathy_log_manager_get_filtered_messages (priv->log_manager,
1680                                                               priv->account,
1681                                                               priv->id,
1682                                                               is_chatroom,
1683                                                               5,
1684                                                               chat_log_filter,
1685                                                               chat);
1686
1687         for (l = messages; l; l = g_list_next (l)) {
1688                 empathy_chat_view_append_message (chat->view, l->data);
1689                 g_object_unref (l->data);
1690         }
1691
1692         g_list_free (messages);
1693
1694         /* Turn back on scrolling */
1695         empathy_chat_view_scroll (chat->view, TRUE);
1696 }
1697
1698 static gint
1699 chat_contacts_completion_func (const gchar *s1,
1700                                const gchar *s2,
1701                                gsize        n)
1702 {
1703         gchar *tmp, *nick1, *nick2;
1704         gint   ret;
1705
1706         if (s1 == s2) {
1707                 return 0;
1708         }
1709         if (!s1 || !s2) {
1710                 return s1 ? -1 : +1;
1711         }
1712
1713         tmp = g_utf8_normalize (s1, -1, G_NORMALIZE_DEFAULT);
1714         nick1 = g_utf8_casefold (tmp, -1);
1715         g_free (tmp);
1716
1717         tmp = g_utf8_normalize (s2, -1, G_NORMALIZE_DEFAULT);
1718         nick2 = g_utf8_casefold (tmp, -1);
1719         g_free (tmp);
1720
1721         ret = strncmp (nick1, nick2, n);
1722
1723         g_free (nick1);
1724         g_free (nick2);
1725
1726         return ret;
1727 }
1728
1729 static gchar *
1730 build_part_message (guint           reason,
1731                     const gchar    *name,
1732                     EmpathyContact *actor,
1733                     const gchar    *message)
1734 {
1735         GString *s = g_string_new ("");
1736         const gchar *actor_name = NULL;
1737
1738         if (actor != NULL) {
1739                 actor_name = empathy_contact_get_name (actor);
1740         }
1741
1742         /* Having an actor only really makes sense for a few actions... */
1743         switch (reason) {
1744         case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
1745                 g_string_append_printf (s, _("%s has disconnected"), name);
1746                 break;
1747         case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED:
1748                 if (actor_name != NULL) {
1749                         /* translators: reverse the order of these arguments
1750                          * if the kicked should come before the kicker in your locale.
1751                          */
1752                         g_string_append_printf (s, _("%1$s was kicked by %2$s"),
1753                                 name, actor_name);
1754                 } else {
1755                         g_string_append_printf (s, _("%s was kicked"), name);
1756                 }
1757                 break;
1758         case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
1759                 if (actor_name != NULL) {
1760                         /* translators: reverse the order of these arguments
1761                          * if the banned should come before the banner in your locale.
1762                          */
1763                         g_string_append_printf (s, _("%1$s was banned by %2$s"),
1764                                 name, actor_name);
1765                 } else {
1766                         g_string_append_printf (s, _("%s was banned"), name);
1767                 }
1768                 break;
1769         default:
1770                 g_string_append_printf (s, _("%s has left the room"), name);
1771         }
1772
1773         if (!EMP_STR_EMPTY (message)) {
1774                 /* Note to translators: this string is appended to
1775                  * notifications like "foo has left the room", with the message
1776                  * given by the user living the room. If this poses a problem,
1777                  * please let us know. :-)
1778                  */
1779                 g_string_append_printf (s, _(" (%s)"), message);
1780         }
1781
1782         return g_string_free (s, FALSE);
1783 }
1784
1785 static void
1786 chat_members_changed_cb (EmpathyTpChat  *tp_chat,
1787                          EmpathyContact *contact,
1788                          EmpathyContact *actor,
1789                          guint           reason,
1790                          gchar          *message,
1791                          gboolean        is_member,
1792                          EmpathyChat    *chat)
1793 {
1794         EmpathyChatPriv *priv = GET_PRIV (chat);
1795         const gchar *name = empathy_contact_get_name (contact);
1796         gchar *str;
1797
1798         if (priv->block_events_timeout_id != 0)
1799                 return;
1800
1801         if (is_member) {
1802                 str = g_strdup_printf (_("%s has joined the room"),
1803                                        name);
1804         } else {
1805                 str = build_part_message (reason, name, actor, message);
1806         }
1807
1808         empathy_chat_view_append_event (chat->view, str);
1809         g_free (str);
1810 }
1811
1812 static gboolean
1813 chat_reset_size_request (gpointer widget)
1814 {
1815         gtk_widget_set_size_request (widget, -1, -1);
1816
1817         return FALSE;
1818 }
1819
1820 static void
1821 chat_update_contacts_visibility (EmpathyChat *chat)
1822 {
1823         EmpathyChatPriv *priv = GET_PRIV (chat);
1824         gboolean show;
1825
1826         show = priv->remote_contact == NULL && priv->show_contacts;
1827
1828         if (!priv->scrolled_window_contacts) {
1829                 return;
1830         }
1831
1832         if (show && priv->contact_list_view == NULL) {
1833                 EmpathyContactListStore *store;
1834                 gint                     min_width;
1835
1836                 /* We are adding the contact list to the chat, we don't want the
1837                  * chat view to become too small. If the chat view is already
1838                  * smaller than 250 make sure that size won't change. If the
1839                  * chat view is bigger the contact list will take some space on
1840                  * it but we make sure the chat view don't become smaller than
1841                  * 250. Relax the size request once the resize is done */
1842                 min_width = MIN (priv->vbox_left->allocation.width, 250);
1843                 gtk_widget_set_size_request (priv->vbox_left, min_width, -1);
1844                 g_idle_add (chat_reset_size_request, priv->vbox_left);
1845
1846                 if (priv->contacts_width > 0) {
1847                         gtk_paned_set_position (GTK_PANED (priv->hpaned),
1848                                                 priv->contacts_width);
1849                 }
1850
1851                 store = empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
1852                 priv->contact_list_view = GTK_WIDGET (empathy_contact_list_view_new (store,
1853                         EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP,
1854                         EMPATHY_CONTACT_FEATURE_CHAT |
1855                         EMPATHY_CONTACT_FEATURE_CALL |
1856                         EMPATHY_CONTACT_FEATURE_LOG |
1857                         EMPATHY_CONTACT_FEATURE_INFO));
1858                 gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
1859                                    priv->contact_list_view);
1860                 gtk_widget_show (priv->contact_list_view);
1861                 gtk_widget_show (priv->scrolled_window_contacts);
1862                 g_object_unref (store);
1863         } else if (!show) {
1864                 priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
1865                 gtk_widget_hide (priv->scrolled_window_contacts);
1866                 if (priv->contact_list_view != NULL) {
1867                         gtk_widget_destroy (priv->contact_list_view);
1868                         priv->contact_list_view = NULL;
1869                 }
1870         }
1871 }
1872
1873 void
1874 empathy_chat_set_show_contacts (EmpathyChat *chat,
1875                                 gboolean     show)
1876 {
1877         EmpathyChatPriv *priv = GET_PRIV (chat);
1878
1879         priv->show_contacts = show;
1880
1881         chat_update_contacts_visibility (chat);
1882
1883         g_object_notify (G_OBJECT (chat), "show-contacts");
1884 }
1885
1886 static void
1887 chat_remote_contact_changed_cb (EmpathyChat *chat)
1888 {
1889         EmpathyChatPriv *priv = GET_PRIV (chat);
1890
1891         if (priv->remote_contact != NULL) {
1892                 g_object_unref (priv->remote_contact);
1893                 priv->remote_contact = NULL;
1894         }
1895
1896         g_free (priv->id);
1897
1898         priv->id = g_strdup (empathy_tp_chat_get_id (priv->tp_chat));
1899         priv->remote_contact = empathy_tp_chat_get_remote_contact (priv->tp_chat);
1900         if (priv->remote_contact != NULL) {
1901                 g_object_ref (priv->remote_contact);
1902                 priv->handle_type = TP_HANDLE_TYPE_CONTACT;
1903         }
1904         else if (priv->tp_chat != NULL) {
1905                 TpChannel *channel;
1906
1907                 channel = empathy_tp_chat_get_channel (priv->tp_chat);
1908                 g_object_get (channel, "handle-type", &priv->handle_type, NULL);
1909         }
1910
1911         chat_update_contacts_visibility (chat);
1912
1913         g_object_notify (G_OBJECT (chat), "remote-contact");
1914         g_object_notify (G_OBJECT (chat), "id");
1915 }
1916
1917 static void
1918 chat_destroy_cb (EmpathyTpChat *tp_chat,
1919                  EmpathyChat   *chat)
1920 {
1921         EmpathyChatPriv *priv;
1922
1923         priv = GET_PRIV (chat);
1924
1925         if (!priv->tp_chat) {
1926                 return;
1927         }
1928
1929         chat_composing_remove_timeout (chat);
1930         g_object_unref (priv->tp_chat);
1931         priv->tp_chat = NULL;
1932         g_object_notify (G_OBJECT (chat), "tp-chat");
1933
1934         empathy_chat_view_append_event (chat->view, _("Disconnected"));
1935         gtk_widget_set_sensitive (chat->input_text_view, FALSE);
1936         empathy_chat_set_show_contacts (chat, FALSE);
1937 }
1938
1939 static void
1940 show_pending_messages (EmpathyChat *chat) {
1941         EmpathyChatPriv *priv = GET_PRIV (chat);
1942         const GList *messages, *l;
1943
1944         if (chat->view == NULL || priv->tp_chat == NULL)
1945                 return;
1946
1947         messages = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1948
1949         for (l = messages; l != NULL ; l = g_list_next (l)) {
1950                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1951                 chat_message_received (chat, message);
1952         }
1953         empathy_tp_chat_acknowledge_messages (priv->tp_chat, messages);
1954 }
1955
1956 static void
1957 chat_create_ui (EmpathyChat *chat)
1958 {
1959         EmpathyChatPriv *priv = GET_PRIV (chat);
1960         GtkBuilder      *gui;
1961         GList           *list = NULL;
1962         gchar           *filename;
1963         GtkTextBuffer   *buffer;
1964
1965         filename = empathy_file_lookup ("empathy-chat.ui",
1966                                         "libempathy-gtk");
1967         gui = empathy_builder_get_file (filename,
1968                                         "chat_widget", &priv->widget,
1969                                         "hpaned", &priv->hpaned,
1970                                         "vbox_left", &priv->vbox_left,
1971                                         "scrolled_window_chat", &priv->scrolled_window_chat,
1972                                         "scrolled_window_input", &priv->scrolled_window_input,
1973                                         "hbox_topic", &priv->hbox_topic,
1974                                         "label_topic", &priv->label_topic,
1975                                         "scrolled_window_contacts", &priv->scrolled_window_contacts,
1976                                         "info_bar_vbox", &priv->info_bar_vbox,
1977                                         NULL);
1978         g_free (filename);
1979
1980         /* Add message view. */
1981         chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
1982         g_signal_connect (chat->view, "focus_in_event",
1983                           G_CALLBACK (chat_text_view_focus_in_event_cb),
1984                           chat);
1985         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_chat),
1986                            GTK_WIDGET (chat->view));
1987         gtk_widget_show (GTK_WIDGET (chat->view));
1988
1989         /* Add input GtkTextView */
1990         chat->input_text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
1991                                               "pixels-above-lines", 2,
1992                                               "pixels-below-lines", 2,
1993                                               "pixels-inside-wrap", 1,
1994                                               "right-margin", 2,
1995                                               "left-margin", 2,
1996                                               "wrap-mode", GTK_WRAP_WORD_CHAR,
1997                                               NULL);
1998         g_signal_connect (chat->input_text_view, "key-press-event",
1999                           G_CALLBACK (chat_input_key_press_event_cb),
2000                           chat);
2001         g_signal_connect (chat->input_text_view, "size-request",
2002                           G_CALLBACK (chat_input_size_request_cb),
2003                           chat);
2004         g_signal_connect (chat->input_text_view, "realize",
2005                           G_CALLBACK (chat_input_realize_cb),
2006                           chat);
2007         g_signal_connect (chat->input_text_view, "populate-popup",
2008                           G_CALLBACK (chat_input_populate_popup_cb),
2009                           chat);
2010         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2011         g_signal_connect (buffer, "changed",
2012                           G_CALLBACK (chat_input_text_buffer_changed_cb),
2013                           chat);
2014         gtk_text_buffer_create_tag (buffer, "misspelled",
2015                                     "underline", PANGO_UNDERLINE_ERROR,
2016                                     NULL);
2017         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
2018                            chat->input_text_view);
2019         gtk_widget_show (chat->input_text_view);
2020
2021         /* Initialy hide the topic, will be shown if not empty */
2022         gtk_widget_hide (priv->hbox_topic);
2023
2024         /* Set widget focus order */
2025         list = g_list_append (NULL, priv->scrolled_window_input);
2026         gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
2027         g_list_free (list);
2028
2029         list = g_list_append (NULL, priv->vbox_left);
2030         list = g_list_append (list, priv->scrolled_window_contacts);
2031         gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
2032         g_list_free (list);
2033
2034         list = g_list_append (NULL, priv->hpaned);
2035         list = g_list_append (list, priv->hbox_topic);
2036         gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
2037         g_list_free (list);
2038
2039         /* Add the main widget in the chat widget */
2040         gtk_container_add (GTK_CONTAINER (chat), priv->widget);
2041         g_object_unref (gui);
2042 }
2043
2044 static void
2045 chat_size_request (GtkWidget      *widget,
2046                    GtkRequisition *requisition)
2047 {
2048   GtkBin *bin = GTK_BIN (widget);
2049   GtkWidget *child;
2050
2051   requisition->width = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2052   requisition->height = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2053
2054   child = gtk_bin_get_child (bin);
2055
2056   if (child && GTK_WIDGET_VISIBLE (child))
2057     {
2058       GtkRequisition child_requisition;
2059
2060       gtk_widget_size_request (child, &child_requisition);
2061
2062       requisition->width += child_requisition.width;
2063       requisition->height += child_requisition.height;
2064     }
2065 }
2066
2067 static void
2068 chat_size_allocate (GtkWidget     *widget,
2069                     GtkAllocation *allocation)
2070 {
2071   GtkBin *bin = GTK_BIN (widget);
2072   GtkAllocation child_allocation;
2073   GtkWidget *child;
2074
2075   widget->allocation = *allocation;
2076
2077   child = gtk_bin_get_child (bin);
2078
2079   if (child && GTK_WIDGET_VISIBLE (child))
2080     {
2081       child_allocation.x = allocation->x + gtk_container_get_border_width (GTK_CONTAINER (widget));
2082       child_allocation.y = allocation->y + gtk_container_get_border_width (GTK_CONTAINER (widget));
2083       child_allocation.width = MAX (allocation->width - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2084       child_allocation.height = MAX (allocation->height - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2085
2086       gtk_widget_size_allocate (child, &child_allocation);
2087     }
2088 }
2089
2090 static void
2091 chat_finalize (GObject *object)
2092 {
2093         EmpathyChat     *chat;
2094         EmpathyChatPriv *priv;
2095
2096         chat = EMPATHY_CHAT (object);
2097         priv = GET_PRIV (chat);
2098
2099         DEBUG ("Finalized: %p", object);
2100
2101         g_list_foreach (priv->input_history, (GFunc) chat_input_history_entry_free, NULL);
2102         g_list_free (priv->input_history);
2103
2104         g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
2105         g_list_free (priv->compositors);
2106
2107         chat_composing_remove_timeout (chat);
2108
2109         g_object_unref (priv->account_manager);
2110         g_object_unref (priv->log_manager);
2111
2112         if (priv->tp_chat) {
2113                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2114                         chat_destroy_cb, chat);
2115                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2116                         chat_message_received_cb, chat);
2117                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2118                         chat_send_error_cb, chat);
2119                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2120                         chat_state_changed_cb, chat);
2121                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2122                         chat_property_changed_cb, chat);
2123                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2124                         chat_members_changed_cb, chat);
2125                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2126                         chat_remote_contact_changed_cb, chat);
2127                 empathy_tp_chat_close (priv->tp_chat);
2128                 g_object_unref (priv->tp_chat);
2129         }
2130         if (priv->account) {
2131                 g_object_unref (priv->account);
2132         }
2133         if (priv->remote_contact) {
2134                 g_object_unref (priv->remote_contact);
2135         }
2136
2137         if (priv->block_events_timeout_id) {
2138                 g_source_remove (priv->block_events_timeout_id);
2139         }
2140
2141         g_free (priv->id);
2142         g_free (priv->name);
2143         g_free (priv->subject);
2144         g_completion_free (priv->completion);
2145
2146         G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
2147 }
2148
2149 static void
2150 chat_constructed (GObject *object)
2151 {
2152         EmpathyChat *chat = EMPATHY_CHAT (object);
2153
2154         chat_add_logs (chat);
2155         show_pending_messages (chat);
2156 }
2157
2158 static void
2159 empathy_chat_class_init (EmpathyChatClass *klass)
2160 {
2161         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2162         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
2163
2164         object_class->finalize = chat_finalize;
2165         object_class->get_property = chat_get_property;
2166         object_class->set_property = chat_set_property;
2167         object_class->constructed = chat_constructed;
2168
2169         widget_class->size_request = chat_size_request;
2170         widget_class->size_allocate = chat_size_allocate;
2171
2172         g_object_class_install_property (object_class,
2173                                          PROP_TP_CHAT,
2174                                          g_param_spec_object ("tp-chat",
2175                                                               "Empathy tp chat",
2176                                                               "The tp chat object",
2177                                                               EMPATHY_TYPE_TP_CHAT,
2178                                                               G_PARAM_CONSTRUCT |
2179                                                               G_PARAM_READWRITE |
2180                                                               G_PARAM_STATIC_STRINGS));
2181         g_object_class_install_property (object_class,
2182                                          PROP_ACCOUNT,
2183                                          g_param_spec_object ("account",
2184                                                               "Account of the chat",
2185                                                               "The account of the chat",
2186                                                               TP_TYPE_ACCOUNT,
2187                                                               G_PARAM_READABLE |
2188                                                               G_PARAM_STATIC_STRINGS));
2189         g_object_class_install_property (object_class,
2190                                          PROP_ID,
2191                                          g_param_spec_string ("id",
2192                                                               "Chat's id",
2193                                                               "The id of the chat",
2194                                                               NULL,
2195                                                               G_PARAM_READABLE |
2196                                                               G_PARAM_STATIC_STRINGS));
2197         g_object_class_install_property (object_class,
2198                                          PROP_NAME,
2199                                          g_param_spec_string ("name",
2200                                                               "Chat's name",
2201                                                               "The name of the chat",
2202                                                               NULL,
2203                                                               G_PARAM_READABLE |
2204                                                               G_PARAM_STATIC_STRINGS));
2205         g_object_class_install_property (object_class,
2206                                          PROP_SUBJECT,
2207                                          g_param_spec_string ("subject",
2208                                                               "Chat's subject",
2209                                                               "The subject or topic of the chat",
2210                                                               NULL,
2211                                                               G_PARAM_READABLE |
2212                                                               G_PARAM_STATIC_STRINGS));
2213         g_object_class_install_property (object_class,
2214                                          PROP_REMOTE_CONTACT,
2215                                          g_param_spec_object ("remote-contact",
2216                                                               "The remote contact",
2217                                                               "The remote contact is any",
2218                                                               EMPATHY_TYPE_CONTACT,
2219                                                               G_PARAM_READABLE |
2220                                                               G_PARAM_STATIC_STRINGS));
2221         g_object_class_install_property (object_class,
2222                                          PROP_SHOW_CONTACTS,
2223                                          g_param_spec_boolean ("show-contacts",
2224                                                                "Contacts' visibility",
2225                                                                "The visibility of the contacts' list",
2226                                                                TRUE,
2227                                                                G_PARAM_READWRITE |
2228                                                                G_PARAM_STATIC_STRINGS));
2229
2230         signals[COMPOSING] =
2231                 g_signal_new ("composing",
2232                               G_OBJECT_CLASS_TYPE (object_class),
2233                               G_SIGNAL_RUN_LAST,
2234                               0,
2235                               NULL, NULL,
2236                               g_cclosure_marshal_VOID__BOOLEAN,
2237                               G_TYPE_NONE,
2238                               1, G_TYPE_BOOLEAN);
2239
2240         signals[NEW_MESSAGE] =
2241                 g_signal_new ("new-message",
2242                               G_OBJECT_CLASS_TYPE (object_class),
2243                               G_SIGNAL_RUN_LAST,
2244                               0,
2245                               NULL, NULL,
2246                               g_cclosure_marshal_VOID__OBJECT,
2247                               G_TYPE_NONE,
2248                               1, EMPATHY_TYPE_MESSAGE);
2249
2250         g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
2251 }
2252
2253 static gboolean
2254 chat_block_events_timeout_cb (gpointer data)
2255 {
2256         EmpathyChatPriv *priv = GET_PRIV (data);
2257
2258         priv->block_events_timeout_id = 0;
2259
2260         return FALSE;
2261 }
2262
2263 static void
2264 account_manager_prepared_cb (GObject *source_object,
2265                              GAsyncResult *result,
2266                              gpointer user_data)
2267 {
2268         GList *accounts, *l;
2269         TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
2270         EmpathyChat *chat = user_data;
2271         GError *error = NULL;
2272
2273         if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
2274                 DEBUG ("Failed to prepare the account manager: %s", error->message);
2275                 g_error_free (error);
2276                 return;
2277         }
2278
2279         accounts = tp_account_manager_get_valid_accounts (account_manager);
2280
2281         for (l = accounts; l != NULL; l = l->next) {
2282                 TpAccount *account = l->data;
2283                 empathy_signal_connect_weak (account, "status-changed",
2284                                              G_CALLBACK (chat_new_connection_cb),
2285                                              G_OBJECT (chat));
2286         }
2287
2288         g_list_free (accounts);
2289 }
2290
2291 static void
2292 empathy_chat_init (EmpathyChat *chat)
2293 {
2294         EmpathyChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
2295                 EMPATHY_TYPE_CHAT, EmpathyChatPriv);
2296
2297         chat->priv = priv;
2298         priv->log_manager = empathy_log_manager_dup_singleton ();
2299         priv->contacts_width = -1;
2300         priv->input_history = NULL;
2301         priv->input_history_current = NULL;
2302         priv->account_manager = tp_account_manager_dup ();
2303
2304         tp_account_manager_prepare_async (priv->account_manager, NULL,
2305                                           account_manager_prepared_cb, chat);
2306
2307         empathy_conf_get_bool (empathy_conf_get (),
2308                                EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS,
2309                                &priv->show_contacts);
2310
2311         /* Block events for some time to avoid having "has come online" or
2312          * "joined" messages. */
2313         priv->block_events_timeout_id =
2314                 g_timeout_add_seconds (1, chat_block_events_timeout_cb, chat);
2315
2316         /* Add nick name completion */
2317         priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_name);
2318         g_completion_set_compare (priv->completion, chat_contacts_completion_func);
2319
2320         chat_create_ui (chat);
2321 }
2322
2323 EmpathyChat *
2324 empathy_chat_new (EmpathyTpChat *tp_chat)
2325 {
2326         return g_object_new (EMPATHY_TYPE_CHAT, "tp-chat", tp_chat, NULL);
2327 }
2328
2329 EmpathyTpChat *
2330 empathy_chat_get_tp_chat (EmpathyChat *chat)
2331 {
2332         EmpathyChatPriv *priv = GET_PRIV (chat);
2333
2334         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2335
2336         return priv->tp_chat;
2337 }
2338
2339 static void display_password_info_bar (EmpathyChat *self,
2340                                        gboolean retry);
2341
2342 static void
2343 provide_password_cb (GObject *tp_chat,
2344                      GAsyncResult *res,
2345                      gpointer user_data)
2346 {
2347         EmpathyChat *self = EMPATHY_CHAT (user_data);
2348         EmpathyChatPriv *priv = GET_PRIV (self);
2349         GError *error = NULL;
2350
2351         if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat), res,
2352                                                       &error)) {
2353                 DEBUG ("error: %s", error->message);
2354                 /* FIXME: what should we do if that's another error? Close the channel?
2355                  * Display the raw D-Bus error to the user isn't very useful */
2356                 if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED))
2357                         display_password_info_bar (self, TRUE);
2358                 g_error_free (error);
2359                 return;
2360         }
2361
2362         /* Room joined */
2363         gtk_widget_set_sensitive (priv->hpaned, TRUE);
2364         gtk_widget_grab_focus (self->input_text_view);
2365 }
2366
2367 static void
2368 password_infobar_response_cb (GtkWidget *info_bar,
2369                               gint response_id,
2370                               EmpathyChat *self)
2371 {
2372         EmpathyChatPriv *priv = GET_PRIV (self);
2373         GtkWidget *entry;
2374         const gchar *password;
2375
2376         if (response_id != GTK_RESPONSE_OK)
2377                 goto out;
2378
2379         entry = g_object_get_data (G_OBJECT (info_bar), "password-entry");
2380         g_assert (entry != NULL);
2381
2382         password = gtk_entry_get_text (GTK_ENTRY (entry));
2383
2384         empathy_tp_chat_provide_password_async (priv->tp_chat, password,
2385                                                 provide_password_cb, self);
2386
2387  out:
2388         gtk_widget_destroy (info_bar);
2389 }
2390
2391 static void
2392 password_entry_activate_cb (GtkWidget *entry,
2393                           GtkWidget *info_bar)
2394 {
2395         gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2396 }
2397
2398 static void
2399 passwd_join_button_cb (GtkButton *button,
2400                           GtkWidget *info_bar)
2401 {
2402         gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2403 }
2404
2405 static void
2406 display_password_info_bar (EmpathyChat *self,
2407                            gboolean retry)
2408 {
2409         EmpathyChatPriv *priv = GET_PRIV (self);
2410         GtkWidget *info_bar;
2411         GtkWidget *content_area;
2412         GtkWidget *hbox;
2413         GtkWidget *image;
2414         GtkWidget *label;
2415         GtkWidget *entry;
2416         GtkWidget *alig;
2417         GtkWidget *button;
2418         GtkMessageType type;
2419         const gchar *msg, *button_label;
2420
2421         if (retry) {
2422                 /* Previous password was wrong */
2423                 type = GTK_MESSAGE_ERROR;
2424                 msg = _("Wrong password; please try again:");
2425                 button_label = _("Retry");
2426         }
2427         else {
2428                 /* First time we're trying to join */
2429                 type = GTK_MESSAGE_QUESTION;
2430                 msg = _("This room is protected by a password:");
2431                 button_label = _("Join");
2432         }
2433
2434         info_bar = gtk_info_bar_new ();
2435         gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), type);
2436
2437         content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2438
2439         hbox = gtk_hbox_new (FALSE, 3);
2440         gtk_container_add (GTK_CONTAINER (content_area), hbox);
2441
2442         /* Add image */
2443         image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
2444                                           GTK_ICON_SIZE_DIALOG);
2445         gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2446
2447         /* Add message */
2448         label = gtk_label_new (msg);
2449         gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
2450
2451         /* Add password entry */
2452         entry = gtk_entry_new ();
2453         gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
2454         gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
2455
2456         g_signal_connect (entry, "activate",
2457                           G_CALLBACK (password_entry_activate_cb), info_bar);
2458
2459         /* Focus the password entry once it's realized */
2460         g_signal_connect (entry, "realize", G_CALLBACK (gtk_widget_grab_focus), NULL);
2461
2462         /* Add 'Join' button */
2463         alig = gtk_alignment_new (0, 0.5, 0, 0);
2464
2465         button = gtk_button_new_with_label (button_label);
2466         gtk_container_add (GTK_CONTAINER (alig), button);
2467         gtk_box_pack_start (GTK_BOX (hbox), alig, FALSE, FALSE, 0);
2468
2469         g_signal_connect (button, "clicked", G_CALLBACK (passwd_join_button_cb),
2470                           info_bar);
2471
2472         g_object_set_data (G_OBJECT (info_bar), "password-entry", entry);
2473
2474         gtk_box_pack_start (GTK_BOX (priv->info_bar_vbox), info_bar,
2475                             FALSE, FALSE, 3);
2476         gtk_widget_show_all (hbox);
2477
2478         g_signal_connect (info_bar, "response",
2479                           G_CALLBACK (password_infobar_response_cb), self);
2480
2481         gtk_widget_show_all (info_bar);
2482 }
2483
2484 static void
2485 chat_password_needed_changed_cb (EmpathyChat *self)
2486 {
2487         EmpathyChatPriv *priv = GET_PRIV (self);
2488
2489         if (empathy_tp_chat_password_needed (priv->tp_chat)) {
2490                 display_password_info_bar (self, FALSE);
2491                 gtk_widget_set_sensitive (priv->hpaned, FALSE);
2492         }
2493 }
2494
2495 void
2496 empathy_chat_set_tp_chat (EmpathyChat   *chat,
2497                           EmpathyTpChat *tp_chat)
2498 {
2499         EmpathyChatPriv *priv = GET_PRIV (chat);
2500         TpConnection    *connection;
2501         GPtrArray       *properties;
2502
2503         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2504         g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
2505         g_return_if_fail (empathy_tp_chat_is_ready (tp_chat));
2506
2507         if (priv->tp_chat) {
2508                 return;
2509         }
2510
2511         if (priv->account) {
2512                 g_object_unref (priv->account);
2513         }
2514
2515         priv->tp_chat = g_object_ref (tp_chat);
2516         connection = empathy_tp_chat_get_connection (priv->tp_chat);
2517         priv->account = g_object_ref (empathy_get_account_for_connection (connection));
2518
2519         g_signal_connect (tp_chat, "destroy",
2520                           G_CALLBACK (chat_destroy_cb),
2521                           chat);
2522         g_signal_connect (tp_chat, "message-received",
2523                           G_CALLBACK (chat_message_received_cb),
2524                           chat);
2525         g_signal_connect (tp_chat, "send-error",
2526                           G_CALLBACK (chat_send_error_cb),
2527                           chat);
2528         g_signal_connect (tp_chat, "chat-state-changed",
2529                           G_CALLBACK (chat_state_changed_cb),
2530                           chat);
2531         g_signal_connect (tp_chat, "property-changed",
2532                           G_CALLBACK (chat_property_changed_cb),
2533                           chat);
2534         g_signal_connect (tp_chat, "members-changed",
2535                           G_CALLBACK (chat_members_changed_cb),
2536                           chat);
2537         g_signal_connect_swapped (tp_chat, "notify::remote-contact",
2538                                   G_CALLBACK (chat_remote_contact_changed_cb),
2539                                   chat);
2540         g_signal_connect_swapped (tp_chat, "notify::password-needed",
2541                                   G_CALLBACK (chat_password_needed_changed_cb),
2542                                   chat);
2543
2544         /* Get initial value of properties */
2545         properties = empathy_tp_chat_get_properties (priv->tp_chat);
2546         if (properties != NULL) {
2547                 guint i;
2548
2549                 for (i = 0; i < properties->len; i++) {
2550                         EmpathyTpChatProperty *property;
2551
2552                         property = g_ptr_array_index (properties, i);
2553                         if (property->value == NULL)
2554                                 continue;
2555
2556                         chat_property_changed_cb (priv->tp_chat,
2557                                                   property->name,
2558                                                   property->value,
2559                                                   chat);
2560                 }
2561         }
2562
2563         chat_remote_contact_changed_cb (chat);
2564
2565         if (chat->input_text_view) {
2566                 gtk_widget_set_sensitive (chat->input_text_view, TRUE);
2567                 if (priv->block_events_timeout_id == 0) {
2568                         empathy_chat_view_append_event (chat->view, _("Connected"));
2569                 }
2570         }
2571
2572         g_object_notify (G_OBJECT (chat), "tp-chat");
2573         g_object_notify (G_OBJECT (chat), "id");
2574         g_object_notify (G_OBJECT (chat), "account");
2575
2576         /* This is a noop when tp-chat is set at object construction time and causes
2577          * the pending messages to be show when it's set on the object after it has
2578          * been created */
2579         show_pending_messages (chat);
2580
2581         /* check if a password is needed */
2582         chat_password_needed_changed_cb (chat);
2583 }
2584
2585 TpAccount *
2586 empathy_chat_get_account (EmpathyChat *chat)
2587 {
2588         EmpathyChatPriv *priv = GET_PRIV (chat);
2589
2590         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2591
2592         return priv->account;
2593 }
2594
2595 const gchar *
2596 empathy_chat_get_id (EmpathyChat *chat)
2597 {
2598         EmpathyChatPriv *priv = GET_PRIV (chat);
2599
2600         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2601
2602         return priv->id;
2603 }
2604
2605 const gchar *
2606 empathy_chat_get_name (EmpathyChat *chat)
2607 {
2608         EmpathyChatPriv *priv = GET_PRIV (chat);
2609         const gchar *ret;
2610
2611         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2612
2613         ret = priv->name;
2614         if (!ret && priv->remote_contact) {
2615                 ret = empathy_contact_get_name (priv->remote_contact);
2616         }
2617
2618         if (!ret)
2619                 ret = priv->id;
2620
2621         return ret ? ret : _("Conversation");
2622 }
2623
2624 const gchar *
2625 empathy_chat_get_subject (EmpathyChat *chat)
2626 {
2627         EmpathyChatPriv *priv = GET_PRIV (chat);
2628
2629         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2630
2631         return priv->subject;
2632 }
2633
2634 EmpathyContact *
2635 empathy_chat_get_remote_contact (EmpathyChat *chat)
2636 {
2637         EmpathyChatPriv *priv = GET_PRIV (chat);
2638
2639         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2640
2641         return priv->remote_contact;
2642 }
2643
2644 GtkWidget *
2645 empathy_chat_get_contact_menu (EmpathyChat *chat)
2646 {
2647         EmpathyChatPriv *priv = GET_PRIV (chat);
2648         GtkWidget       *menu = NULL;
2649
2650         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2651
2652         if (priv->remote_contact) {
2653                 menu = empathy_contact_menu_new (priv->remote_contact,
2654                                                  EMPATHY_CONTACT_FEATURE_CALL |
2655                                                  EMPATHY_CONTACT_FEATURE_LOG |
2656                                                  EMPATHY_CONTACT_FEATURE_INFO);
2657         }
2658         else if (priv->contact_list_view) {
2659                 EmpathyContactListView *view;
2660
2661                 view = EMPATHY_CONTACT_LIST_VIEW (priv->contact_list_view);
2662                 menu = empathy_contact_list_view_get_contact_menu (view);
2663         }
2664
2665         return menu;
2666 }
2667
2668 void
2669 empathy_chat_clear (EmpathyChat *chat)
2670 {
2671         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2672
2673         empathy_chat_view_clear (chat->view);
2674 }
2675
2676 void
2677 empathy_chat_scroll_down (EmpathyChat *chat)
2678 {
2679         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2680
2681         empathy_chat_view_scroll_down (chat->view);
2682 }
2683
2684 void
2685 empathy_chat_cut (EmpathyChat *chat)
2686 {
2687         GtkTextBuffer *buffer;
2688
2689         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2690
2691         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2692         if (gtk_text_buffer_get_has_selection (buffer)) {
2693                 GtkClipboard *clipboard;
2694
2695                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2696
2697                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
2698         }
2699 }
2700
2701 void
2702 empathy_chat_copy (EmpathyChat *chat)
2703 {
2704         GtkTextBuffer *buffer;
2705
2706         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2707
2708         if (empathy_chat_view_get_has_selection (chat->view)) {
2709                 empathy_chat_view_copy_clipboard (chat->view);
2710                 return;
2711         }
2712
2713         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2714         if (gtk_text_buffer_get_has_selection (buffer)) {
2715                 GtkClipboard *clipboard;
2716
2717                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2718
2719                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2720         }
2721 }
2722
2723 void
2724 empathy_chat_paste (EmpathyChat *chat)
2725 {
2726         GtkTextBuffer *buffer;
2727         GtkClipboard  *clipboard;
2728         EmpathyChatPriv *priv;
2729
2730         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2731
2732         priv = GET_PRIV (chat);
2733
2734         if (priv->tp_chat == NULL ||
2735             !GTK_WIDGET_IS_SENSITIVE (chat->input_text_view))
2736                 return;
2737
2738         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2739         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2740
2741         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
2742 }
2743
2744 void
2745 empathy_chat_correct_word (EmpathyChat  *chat,
2746                           GtkTextIter *start,
2747                           GtkTextIter *end,
2748                           const gchar *new_word)
2749 {
2750         GtkTextBuffer *buffer;
2751
2752         g_return_if_fail (chat != NULL);
2753         g_return_if_fail (new_word != NULL);
2754
2755         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2756
2757         gtk_text_buffer_delete (buffer, start, end);
2758         gtk_text_buffer_insert (buffer, start,
2759                                 new_word,
2760                                 -1);
2761 }
2762
2763 gboolean
2764 empathy_chat_is_room (EmpathyChat *chat)
2765 {
2766         EmpathyChatPriv *priv = GET_PRIV (chat);
2767
2768         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
2769
2770         return (priv->handle_type == TP_HANDLE_TYPE_ROOM);
2771 }
2772