]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat.c
account-widget-sip: Add UI to configure auth-user, proxy-host and port params (#601697)
[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         g_return_if_fail (TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED != reason);
1799
1800         if (priv->block_events_timeout_id != 0)
1801                 return;
1802
1803         if (is_member) {
1804                 str = g_strdup_printf (_("%s has joined the room"),
1805                                        name);
1806         } else {
1807                 str = build_part_message (reason, name, actor, message);
1808         }
1809
1810         empathy_chat_view_append_event (chat->view, str);
1811         g_free (str);
1812 }
1813
1814 static void
1815 chat_member_renamed_cb (EmpathyTpChat  *tp_chat,
1816                          EmpathyContact *old_contact,
1817                          EmpathyContact *new_contact,
1818                          guint           reason,
1819                          gchar          *message,
1820                          EmpathyChat    *chat)
1821 {
1822         EmpathyChatPriv *priv = GET_PRIV (chat);
1823
1824         g_return_if_fail (TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED == reason);
1825
1826         if (priv->block_events_timeout_id == 0) {
1827                 gchar *str;
1828
1829                 str = g_strdup_printf (_("%s is now known as %s"),
1830                                        empathy_contact_get_name (old_contact),
1831                                        empathy_contact_get_name (new_contact));
1832                 empathy_chat_view_append_event (chat->view, str);
1833                 g_free (str);
1834         }
1835
1836 }
1837
1838 static gboolean
1839 chat_reset_size_request (gpointer widget)
1840 {
1841         gtk_widget_set_size_request (widget, -1, -1);
1842
1843         return FALSE;
1844 }
1845
1846 static void
1847 chat_update_contacts_visibility (EmpathyChat *chat)
1848 {
1849         EmpathyChatPriv *priv = GET_PRIV (chat);
1850         gboolean show;
1851         GtkAllocation allocation;
1852
1853         show = priv->remote_contact == NULL && priv->show_contacts;
1854
1855         if (!priv->scrolled_window_contacts) {
1856                 return;
1857         }
1858
1859         if (show && priv->contact_list_view == NULL) {
1860                 EmpathyContactListStore *store;
1861                 gint                     min_width;
1862
1863                 /* We are adding the contact list to the chat, we don't want the
1864                  * chat view to become too small. If the chat view is already
1865                  * smaller than 250 make sure that size won't change. If the
1866                  * chat view is bigger the contact list will take some space on
1867                  * it but we make sure the chat view don't become smaller than
1868                  * 250. Relax the size request once the resize is done */
1869                 gtk_widget_get_allocation (priv->vbox_left, &allocation);
1870                 min_width = MIN (allocation.width, 250);
1871                 gtk_widget_set_size_request (priv->vbox_left, min_width, -1);
1872                 g_idle_add (chat_reset_size_request, priv->vbox_left);
1873
1874                 if (priv->contacts_width > 0) {
1875                         gtk_paned_set_position (GTK_PANED (priv->hpaned),
1876                                                 priv->contacts_width);
1877                 }
1878
1879                 store = empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
1880                 priv->contact_list_view = GTK_WIDGET (empathy_contact_list_view_new (store,
1881                         EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP,
1882                         EMPATHY_CONTACT_FEATURE_CHAT |
1883                         EMPATHY_CONTACT_FEATURE_CALL |
1884                         EMPATHY_CONTACT_FEATURE_LOG |
1885                         EMPATHY_CONTACT_FEATURE_INFO));
1886                 gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
1887                                    priv->contact_list_view);
1888                 gtk_widget_show (priv->contact_list_view);
1889                 gtk_widget_show (priv->scrolled_window_contacts);
1890                 g_object_unref (store);
1891         } else if (!show) {
1892                 priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
1893                 gtk_widget_hide (priv->scrolled_window_contacts);
1894                 if (priv->contact_list_view != NULL) {
1895                         gtk_widget_destroy (priv->contact_list_view);
1896                         priv->contact_list_view = NULL;
1897                 }
1898         }
1899 }
1900
1901 void
1902 empathy_chat_set_show_contacts (EmpathyChat *chat,
1903                                 gboolean     show)
1904 {
1905         EmpathyChatPriv *priv = GET_PRIV (chat);
1906
1907         priv->show_contacts = show;
1908
1909         chat_update_contacts_visibility (chat);
1910
1911         g_object_notify (G_OBJECT (chat), "show-contacts");
1912 }
1913
1914 static void
1915 chat_remote_contact_changed_cb (EmpathyChat *chat)
1916 {
1917         EmpathyChatPriv *priv = GET_PRIV (chat);
1918
1919         if (priv->remote_contact != NULL) {
1920                 g_object_unref (priv->remote_contact);
1921                 priv->remote_contact = NULL;
1922         }
1923
1924         g_free (priv->id);
1925
1926         priv->id = g_strdup (empathy_tp_chat_get_id (priv->tp_chat));
1927         priv->remote_contact = empathy_tp_chat_get_remote_contact (priv->tp_chat);
1928         if (priv->remote_contact != NULL) {
1929                 g_object_ref (priv->remote_contact);
1930                 priv->handle_type = TP_HANDLE_TYPE_CONTACT;
1931         }
1932         else if (priv->tp_chat != NULL) {
1933                 TpChannel *channel;
1934
1935                 channel = empathy_tp_chat_get_channel (priv->tp_chat);
1936                 g_object_get (channel, "handle-type", &priv->handle_type, NULL);
1937         }
1938
1939         chat_update_contacts_visibility (chat);
1940
1941         g_object_notify (G_OBJECT (chat), "remote-contact");
1942         g_object_notify (G_OBJECT (chat), "id");
1943 }
1944
1945 static void
1946 chat_destroy_cb (EmpathyTpChat *tp_chat,
1947                  EmpathyChat   *chat)
1948 {
1949         EmpathyChatPriv *priv;
1950
1951         priv = GET_PRIV (chat);
1952
1953         if (!priv->tp_chat) {
1954                 return;
1955         }
1956
1957         chat_composing_remove_timeout (chat);
1958         g_object_unref (priv->tp_chat);
1959         priv->tp_chat = NULL;
1960         g_object_notify (G_OBJECT (chat), "tp-chat");
1961
1962         empathy_chat_view_append_event (chat->view, _("Disconnected"));
1963         gtk_widget_set_sensitive (chat->input_text_view, FALSE);
1964         empathy_chat_set_show_contacts (chat, FALSE);
1965 }
1966
1967 static void
1968 show_pending_messages (EmpathyChat *chat) {
1969         EmpathyChatPriv *priv = GET_PRIV (chat);
1970         const GList *messages, *l;
1971
1972         if (chat->view == NULL || priv->tp_chat == NULL)
1973                 return;
1974
1975         messages = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1976
1977         for (l = messages; l != NULL ; l = g_list_next (l)) {
1978                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1979                 chat_message_received (chat, message);
1980         }
1981         empathy_tp_chat_acknowledge_messages (priv->tp_chat, messages);
1982 }
1983
1984 static void
1985 chat_create_ui (EmpathyChat *chat)
1986 {
1987         EmpathyChatPriv *priv = GET_PRIV (chat);
1988         GtkBuilder      *gui;
1989         GList           *list = NULL;
1990         gchar           *filename;
1991         GtkTextBuffer   *buffer;
1992
1993         filename = empathy_file_lookup ("empathy-chat.ui",
1994                                         "libempathy-gtk");
1995         gui = empathy_builder_get_file (filename,
1996                                         "chat_widget", &priv->widget,
1997                                         "hpaned", &priv->hpaned,
1998                                         "vbox_left", &priv->vbox_left,
1999                                         "scrolled_window_chat", &priv->scrolled_window_chat,
2000                                         "scrolled_window_input", &priv->scrolled_window_input,
2001                                         "hbox_topic", &priv->hbox_topic,
2002                                         "label_topic", &priv->label_topic,
2003                                         "scrolled_window_contacts", &priv->scrolled_window_contacts,
2004                                         "info_bar_vbox", &priv->info_bar_vbox,
2005                                         NULL);
2006         g_free (filename);
2007
2008         /* Add message view. */
2009         chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
2010         g_signal_connect (chat->view, "focus_in_event",
2011                           G_CALLBACK (chat_text_view_focus_in_event_cb),
2012                           chat);
2013         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_chat),
2014                            GTK_WIDGET (chat->view));
2015         gtk_widget_show (GTK_WIDGET (chat->view));
2016
2017         /* Add input GtkTextView */
2018         chat->input_text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
2019                                               "pixels-above-lines", 2,
2020                                               "pixels-below-lines", 2,
2021                                               "pixels-inside-wrap", 1,
2022                                               "right-margin", 2,
2023                                               "left-margin", 2,
2024                                               "wrap-mode", GTK_WRAP_WORD_CHAR,
2025                                               NULL);
2026         g_signal_connect (chat->input_text_view, "key-press-event",
2027                           G_CALLBACK (chat_input_key_press_event_cb),
2028                           chat);
2029         g_signal_connect (chat->input_text_view, "size-request",
2030                           G_CALLBACK (chat_input_size_request_cb),
2031                           chat);
2032         g_signal_connect (chat->input_text_view, "realize",
2033                           G_CALLBACK (chat_input_realize_cb),
2034                           chat);
2035         g_signal_connect (chat->input_text_view, "populate-popup",
2036                           G_CALLBACK (chat_input_populate_popup_cb),
2037                           chat);
2038         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2039         g_signal_connect (buffer, "changed",
2040                           G_CALLBACK (chat_input_text_buffer_changed_cb),
2041                           chat);
2042         gtk_text_buffer_create_tag (buffer, "misspelled",
2043                                     "underline", PANGO_UNDERLINE_ERROR,
2044                                     NULL);
2045         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
2046                            chat->input_text_view);
2047         gtk_widget_show (chat->input_text_view);
2048
2049         /* Initialy hide the topic, will be shown if not empty */
2050         gtk_widget_hide (priv->hbox_topic);
2051
2052         /* Set widget focus order */
2053         list = g_list_append (NULL, priv->scrolled_window_input);
2054         gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
2055         g_list_free (list);
2056
2057         list = g_list_append (NULL, priv->vbox_left);
2058         list = g_list_append (list, priv->scrolled_window_contacts);
2059         gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
2060         g_list_free (list);
2061
2062         list = g_list_append (NULL, priv->hpaned);
2063         list = g_list_append (list, priv->hbox_topic);
2064         gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
2065         g_list_free (list);
2066
2067         /* Add the main widget in the chat widget */
2068         gtk_container_add (GTK_CONTAINER (chat), priv->widget);
2069         g_object_unref (gui);
2070 }
2071
2072 static void
2073 chat_size_request (GtkWidget      *widget,
2074                    GtkRequisition *requisition)
2075 {
2076   GtkBin *bin = GTK_BIN (widget);
2077   GtkWidget *child;
2078
2079   requisition->width = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2080   requisition->height = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2081
2082   child = gtk_bin_get_child (bin);
2083
2084   if (child && gtk_widget_get_visible (child))
2085     {
2086       GtkRequisition child_requisition;
2087
2088       gtk_widget_size_request (child, &child_requisition);
2089
2090       requisition->width += child_requisition.width;
2091       requisition->height += child_requisition.height;
2092     }
2093 }
2094
2095 static void
2096 chat_size_allocate (GtkWidget     *widget,
2097                     GtkAllocation *allocation)
2098 {
2099   GtkBin *bin = GTK_BIN (widget);
2100   GtkAllocation child_allocation;
2101   GtkWidget *child;
2102
2103   gtk_widget_set_allocation (widget, allocation);
2104
2105   child = gtk_bin_get_child (bin);
2106
2107   if (child && gtk_widget_get_visible (child))
2108     {
2109       child_allocation.x = allocation->x + gtk_container_get_border_width (GTK_CONTAINER (widget));
2110       child_allocation.y = allocation->y + gtk_container_get_border_width (GTK_CONTAINER (widget));
2111       child_allocation.width = MAX (allocation->width - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2112       child_allocation.height = MAX (allocation->height - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2113
2114       gtk_widget_size_allocate (child, &child_allocation);
2115     }
2116 }
2117
2118 static void
2119 chat_finalize (GObject *object)
2120 {
2121         EmpathyChat     *chat;
2122         EmpathyChatPriv *priv;
2123
2124         chat = EMPATHY_CHAT (object);
2125         priv = GET_PRIV (chat);
2126
2127         DEBUG ("Finalized: %p", object);
2128
2129         g_list_foreach (priv->input_history, (GFunc) chat_input_history_entry_free, NULL);
2130         g_list_free (priv->input_history);
2131
2132         g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
2133         g_list_free (priv->compositors);
2134
2135         chat_composing_remove_timeout (chat);
2136
2137         g_object_unref (priv->account_manager);
2138         g_object_unref (priv->log_manager);
2139
2140         if (priv->tp_chat) {
2141                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2142                         chat_destroy_cb, chat);
2143                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2144                         chat_message_received_cb, chat);
2145                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2146                         chat_send_error_cb, chat);
2147                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2148                         chat_state_changed_cb, chat);
2149                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2150                         chat_property_changed_cb, chat);
2151                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2152                         chat_members_changed_cb, chat);
2153                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2154                         chat_remote_contact_changed_cb, chat);
2155                 empathy_tp_chat_close (priv->tp_chat);
2156                 g_object_unref (priv->tp_chat);
2157         }
2158         if (priv->account) {
2159                 g_object_unref (priv->account);
2160         }
2161         if (priv->remote_contact) {
2162                 g_object_unref (priv->remote_contact);
2163         }
2164
2165         if (priv->block_events_timeout_id) {
2166                 g_source_remove (priv->block_events_timeout_id);
2167         }
2168
2169         g_free (priv->id);
2170         g_free (priv->name);
2171         g_free (priv->subject);
2172         g_completion_free (priv->completion);
2173
2174         G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
2175 }
2176
2177 static void
2178 chat_constructed (GObject *object)
2179 {
2180         EmpathyChat *chat = EMPATHY_CHAT (object);
2181
2182         chat_add_logs (chat);
2183         show_pending_messages (chat);
2184 }
2185
2186 static void
2187 empathy_chat_class_init (EmpathyChatClass *klass)
2188 {
2189         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2190         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
2191
2192         object_class->finalize = chat_finalize;
2193         object_class->get_property = chat_get_property;
2194         object_class->set_property = chat_set_property;
2195         object_class->constructed = chat_constructed;
2196
2197         widget_class->size_request = chat_size_request;
2198         widget_class->size_allocate = chat_size_allocate;
2199
2200         g_object_class_install_property (object_class,
2201                                          PROP_TP_CHAT,
2202                                          g_param_spec_object ("tp-chat",
2203                                                               "Empathy tp chat",
2204                                                               "The tp chat object",
2205                                                               EMPATHY_TYPE_TP_CHAT,
2206                                                               G_PARAM_CONSTRUCT |
2207                                                               G_PARAM_READWRITE |
2208                                                               G_PARAM_STATIC_STRINGS));
2209         g_object_class_install_property (object_class,
2210                                          PROP_ACCOUNT,
2211                                          g_param_spec_object ("account",
2212                                                               "Account of the chat",
2213                                                               "The account of the chat",
2214                                                               TP_TYPE_ACCOUNT,
2215                                                               G_PARAM_READABLE |
2216                                                               G_PARAM_STATIC_STRINGS));
2217         g_object_class_install_property (object_class,
2218                                          PROP_ID,
2219                                          g_param_spec_string ("id",
2220                                                               "Chat's id",
2221                                                               "The id of the chat",
2222                                                               NULL,
2223                                                               G_PARAM_READABLE |
2224                                                               G_PARAM_STATIC_STRINGS));
2225         g_object_class_install_property (object_class,
2226                                          PROP_NAME,
2227                                          g_param_spec_string ("name",
2228                                                               "Chat's name",
2229                                                               "The name of the chat",
2230                                                               NULL,
2231                                                               G_PARAM_READABLE |
2232                                                               G_PARAM_STATIC_STRINGS));
2233         g_object_class_install_property (object_class,
2234                                          PROP_SUBJECT,
2235                                          g_param_spec_string ("subject",
2236                                                               "Chat's subject",
2237                                                               "The subject or topic of the chat",
2238                                                               NULL,
2239                                                               G_PARAM_READABLE |
2240                                                               G_PARAM_STATIC_STRINGS));
2241         g_object_class_install_property (object_class,
2242                                          PROP_REMOTE_CONTACT,
2243                                          g_param_spec_object ("remote-contact",
2244                                                               "The remote contact",
2245                                                               "The remote contact is any",
2246                                                               EMPATHY_TYPE_CONTACT,
2247                                                               G_PARAM_READABLE |
2248                                                               G_PARAM_STATIC_STRINGS));
2249         g_object_class_install_property (object_class,
2250                                          PROP_SHOW_CONTACTS,
2251                                          g_param_spec_boolean ("show-contacts",
2252                                                                "Contacts' visibility",
2253                                                                "The visibility of the contacts' list",
2254                                                                TRUE,
2255                                                                G_PARAM_READWRITE |
2256                                                                G_PARAM_STATIC_STRINGS));
2257
2258         signals[COMPOSING] =
2259                 g_signal_new ("composing",
2260                               G_OBJECT_CLASS_TYPE (object_class),
2261                               G_SIGNAL_RUN_LAST,
2262                               0,
2263                               NULL, NULL,
2264                               g_cclosure_marshal_VOID__BOOLEAN,
2265                               G_TYPE_NONE,
2266                               1, G_TYPE_BOOLEAN);
2267
2268         signals[NEW_MESSAGE] =
2269                 g_signal_new ("new-message",
2270                               G_OBJECT_CLASS_TYPE (object_class),
2271                               G_SIGNAL_RUN_LAST,
2272                               0,
2273                               NULL, NULL,
2274                               g_cclosure_marshal_VOID__OBJECT,
2275                               G_TYPE_NONE,
2276                               1, EMPATHY_TYPE_MESSAGE);
2277
2278         g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
2279 }
2280
2281 static gboolean
2282 chat_block_events_timeout_cb (gpointer data)
2283 {
2284         EmpathyChatPriv *priv = GET_PRIV (data);
2285
2286         priv->block_events_timeout_id = 0;
2287
2288         return FALSE;
2289 }
2290
2291 static void
2292 account_manager_prepared_cb (GObject *source_object,
2293                              GAsyncResult *result,
2294                              gpointer user_data)
2295 {
2296         GList *accounts, *l;
2297         TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
2298         EmpathyChat *chat = user_data;
2299         GError *error = NULL;
2300
2301         if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
2302                 DEBUG ("Failed to prepare the account manager: %s", error->message);
2303                 g_error_free (error);
2304                 return;
2305         }
2306
2307         accounts = tp_account_manager_get_valid_accounts (account_manager);
2308
2309         for (l = accounts; l != NULL; l = l->next) {
2310                 TpAccount *account = l->data;
2311                 empathy_signal_connect_weak (account, "status-changed",
2312                                              G_CALLBACK (chat_new_connection_cb),
2313                                              G_OBJECT (chat));
2314         }
2315
2316         g_list_free (accounts);
2317 }
2318
2319 static void
2320 empathy_chat_init (EmpathyChat *chat)
2321 {
2322         EmpathyChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
2323                 EMPATHY_TYPE_CHAT, EmpathyChatPriv);
2324
2325         chat->priv = priv;
2326         priv->log_manager = empathy_log_manager_dup_singleton ();
2327         priv->contacts_width = -1;
2328         priv->input_history = NULL;
2329         priv->input_history_current = NULL;
2330         priv->account_manager = tp_account_manager_dup ();
2331
2332         tp_account_manager_prepare_async (priv->account_manager, NULL,
2333                                           account_manager_prepared_cb, chat);
2334
2335         empathy_conf_get_bool (empathy_conf_get (),
2336                                EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS,
2337                                &priv->show_contacts);
2338
2339         /* Block events for some time to avoid having "has come online" or
2340          * "joined" messages. */
2341         priv->block_events_timeout_id =
2342                 g_timeout_add_seconds (1, chat_block_events_timeout_cb, chat);
2343
2344         /* Add nick name completion */
2345         priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_name);
2346         g_completion_set_compare (priv->completion, chat_contacts_completion_func);
2347
2348         chat_create_ui (chat);
2349 }
2350
2351 EmpathyChat *
2352 empathy_chat_new (EmpathyTpChat *tp_chat)
2353 {
2354         return g_object_new (EMPATHY_TYPE_CHAT, "tp-chat", tp_chat, NULL);
2355 }
2356
2357 EmpathyTpChat *
2358 empathy_chat_get_tp_chat (EmpathyChat *chat)
2359 {
2360         EmpathyChatPriv *priv = GET_PRIV (chat);
2361
2362         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2363
2364         return priv->tp_chat;
2365 }
2366
2367 static void display_password_info_bar (EmpathyChat *self,
2368                                        gboolean retry);
2369
2370 static void
2371 provide_password_cb (GObject *tp_chat,
2372                      GAsyncResult *res,
2373                      gpointer user_data)
2374 {
2375         EmpathyChat *self = EMPATHY_CHAT (user_data);
2376         EmpathyChatPriv *priv = GET_PRIV (self);
2377         GError *error = NULL;
2378
2379         if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat), res,
2380                                                       &error)) {
2381                 DEBUG ("error: %s", error->message);
2382                 /* FIXME: what should we do if that's another error? Close the channel?
2383                  * Display the raw D-Bus error to the user isn't very useful */
2384                 if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED))
2385                         display_password_info_bar (self, TRUE);
2386                 g_error_free (error);
2387                 return;
2388         }
2389
2390         /* Room joined */
2391         gtk_widget_set_sensitive (priv->hpaned, TRUE);
2392         gtk_widget_grab_focus (self->input_text_view);
2393 }
2394
2395 static void
2396 password_infobar_response_cb (GtkWidget *info_bar,
2397                               gint response_id,
2398                               EmpathyChat *self)
2399 {
2400         EmpathyChatPriv *priv = GET_PRIV (self);
2401         GtkWidget *entry;
2402         const gchar *password;
2403
2404         if (response_id != GTK_RESPONSE_OK)
2405                 goto out;
2406
2407         entry = g_object_get_data (G_OBJECT (info_bar), "password-entry");
2408         g_assert (entry != NULL);
2409
2410         password = gtk_entry_get_text (GTK_ENTRY (entry));
2411
2412         empathy_tp_chat_provide_password_async (priv->tp_chat, password,
2413                                                 provide_password_cb, self);
2414
2415  out:
2416         gtk_widget_destroy (info_bar);
2417 }
2418
2419 static void
2420 password_entry_activate_cb (GtkWidget *entry,
2421                           GtkWidget *info_bar)
2422 {
2423         gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2424 }
2425
2426 static void
2427 passwd_join_button_cb (GtkButton *button,
2428                           GtkWidget *info_bar)
2429 {
2430         gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2431 }
2432
2433 static void
2434 display_password_info_bar (EmpathyChat *self,
2435                            gboolean retry)
2436 {
2437         EmpathyChatPriv *priv = GET_PRIV (self);
2438         GtkWidget *info_bar;
2439         GtkWidget *content_area;
2440         GtkWidget *hbox;
2441         GtkWidget *image;
2442         GtkWidget *label;
2443         GtkWidget *entry;
2444         GtkWidget *alig;
2445         GtkWidget *button;
2446         GtkMessageType type;
2447         const gchar *msg, *button_label;
2448
2449         if (retry) {
2450                 /* Previous password was wrong */
2451                 type = GTK_MESSAGE_ERROR;
2452                 msg = _("Wrong password; please try again:");
2453                 button_label = _("Retry");
2454         }
2455         else {
2456                 /* First time we're trying to join */
2457                 type = GTK_MESSAGE_QUESTION;
2458                 msg = _("This room is protected by a password:");
2459                 button_label = _("Join");
2460         }
2461
2462         info_bar = gtk_info_bar_new ();
2463         gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), type);
2464
2465         content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2466
2467         hbox = gtk_hbox_new (FALSE, 3);
2468         gtk_container_add (GTK_CONTAINER (content_area), hbox);
2469
2470         /* Add image */
2471         image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
2472                                           GTK_ICON_SIZE_DIALOG);
2473         gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2474
2475         /* Add message */
2476         label = gtk_label_new (msg);
2477         gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
2478
2479         /* Add password entry */
2480         entry = gtk_entry_new ();
2481         gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
2482         gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
2483
2484         g_signal_connect (entry, "activate",
2485                           G_CALLBACK (password_entry_activate_cb), info_bar);
2486
2487         /* Focus the password entry once it's realized */
2488         g_signal_connect (entry, "realize", G_CALLBACK (gtk_widget_grab_focus), NULL);
2489
2490         /* Add 'Join' button */
2491         alig = gtk_alignment_new (0, 0.5, 0, 0);
2492
2493         button = gtk_button_new_with_label (button_label);
2494         gtk_container_add (GTK_CONTAINER (alig), button);
2495         gtk_box_pack_start (GTK_BOX (hbox), alig, FALSE, FALSE, 0);
2496
2497         g_signal_connect (button, "clicked", G_CALLBACK (passwd_join_button_cb),
2498                           info_bar);
2499
2500         g_object_set_data (G_OBJECT (info_bar), "password-entry", entry);
2501
2502         gtk_box_pack_start (GTK_BOX (priv->info_bar_vbox), info_bar,
2503                             FALSE, FALSE, 3);
2504         gtk_widget_show_all (hbox);
2505
2506         g_signal_connect (info_bar, "response",
2507                           G_CALLBACK (password_infobar_response_cb), self);
2508
2509         gtk_widget_show_all (info_bar);
2510 }
2511
2512 static void
2513 chat_password_needed_changed_cb (EmpathyChat *self)
2514 {
2515         EmpathyChatPriv *priv = GET_PRIV (self);
2516
2517         if (empathy_tp_chat_password_needed (priv->tp_chat)) {
2518                 display_password_info_bar (self, FALSE);
2519                 gtk_widget_set_sensitive (priv->hpaned, FALSE);
2520         }
2521 }
2522
2523 void
2524 empathy_chat_set_tp_chat (EmpathyChat   *chat,
2525                           EmpathyTpChat *tp_chat)
2526 {
2527         EmpathyChatPriv *priv = GET_PRIV (chat);
2528         TpConnection    *connection;
2529         GPtrArray       *properties;
2530
2531         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2532         g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
2533         g_return_if_fail (empathy_tp_chat_is_ready (tp_chat));
2534
2535         if (priv->tp_chat) {
2536                 return;
2537         }
2538
2539         if (priv->account) {
2540                 g_object_unref (priv->account);
2541         }
2542
2543         priv->tp_chat = g_object_ref (tp_chat);
2544         connection = empathy_tp_chat_get_connection (priv->tp_chat);
2545         priv->account = g_object_ref (empathy_get_account_for_connection (connection));
2546
2547         g_signal_connect (tp_chat, "destroy",
2548                           G_CALLBACK (chat_destroy_cb),
2549                           chat);
2550         g_signal_connect (tp_chat, "message-received",
2551                           G_CALLBACK (chat_message_received_cb),
2552                           chat);
2553         g_signal_connect (tp_chat, "send-error",
2554                           G_CALLBACK (chat_send_error_cb),
2555                           chat);
2556         g_signal_connect (tp_chat, "chat-state-changed",
2557                           G_CALLBACK (chat_state_changed_cb),
2558                           chat);
2559         g_signal_connect (tp_chat, "property-changed",
2560                           G_CALLBACK (chat_property_changed_cb),
2561                           chat);
2562         g_signal_connect (tp_chat, "members-changed",
2563                           G_CALLBACK (chat_members_changed_cb),
2564                           chat);
2565         g_signal_connect (tp_chat, "member-renamed",
2566                           G_CALLBACK (chat_member_renamed_cb),
2567                           chat);
2568         g_signal_connect_swapped (tp_chat, "notify::remote-contact",
2569                                   G_CALLBACK (chat_remote_contact_changed_cb),
2570                                   chat);
2571         g_signal_connect_swapped (tp_chat, "notify::password-needed",
2572                                   G_CALLBACK (chat_password_needed_changed_cb),
2573                                   chat);
2574
2575         /* Get initial value of properties */
2576         properties = empathy_tp_chat_get_properties (priv->tp_chat);
2577         if (properties != NULL) {
2578                 guint i;
2579
2580                 for (i = 0; i < properties->len; i++) {
2581                         EmpathyTpChatProperty *property;
2582
2583                         property = g_ptr_array_index (properties, i);
2584                         if (property->value == NULL)
2585                                 continue;
2586
2587                         chat_property_changed_cb (priv->tp_chat,
2588                                                   property->name,
2589                                                   property->value,
2590                                                   chat);
2591                 }
2592         }
2593
2594         chat_remote_contact_changed_cb (chat);
2595
2596         if (chat->input_text_view) {
2597                 gtk_widget_set_sensitive (chat->input_text_view, TRUE);
2598                 if (priv->block_events_timeout_id == 0) {
2599                         empathy_chat_view_append_event (chat->view, _("Connected"));
2600                 }
2601         }
2602
2603         g_object_notify (G_OBJECT (chat), "tp-chat");
2604         g_object_notify (G_OBJECT (chat), "id");
2605         g_object_notify (G_OBJECT (chat), "account");
2606
2607         /* This is a noop when tp-chat is set at object construction time and causes
2608          * the pending messages to be show when it's set on the object after it has
2609          * been created */
2610         show_pending_messages (chat);
2611
2612         /* check if a password is needed */
2613         chat_password_needed_changed_cb (chat);
2614 }
2615
2616 TpAccount *
2617 empathy_chat_get_account (EmpathyChat *chat)
2618 {
2619         EmpathyChatPriv *priv = GET_PRIV (chat);
2620
2621         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2622
2623         return priv->account;
2624 }
2625
2626 const gchar *
2627 empathy_chat_get_id (EmpathyChat *chat)
2628 {
2629         EmpathyChatPriv *priv = GET_PRIV (chat);
2630
2631         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2632
2633         return priv->id;
2634 }
2635
2636 const gchar *
2637 empathy_chat_get_name (EmpathyChat *chat)
2638 {
2639         EmpathyChatPriv *priv = GET_PRIV (chat);
2640         const gchar *ret;
2641
2642         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2643
2644         ret = priv->name;
2645         if (!ret && priv->remote_contact) {
2646                 ret = empathy_contact_get_name (priv->remote_contact);
2647         }
2648
2649         if (!ret)
2650                 ret = priv->id;
2651
2652         return ret ? ret : _("Conversation");
2653 }
2654
2655 const gchar *
2656 empathy_chat_get_subject (EmpathyChat *chat)
2657 {
2658         EmpathyChatPriv *priv = GET_PRIV (chat);
2659
2660         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2661
2662         return priv->subject;
2663 }
2664
2665 EmpathyContact *
2666 empathy_chat_get_remote_contact (EmpathyChat *chat)
2667 {
2668         EmpathyChatPriv *priv = GET_PRIV (chat);
2669
2670         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2671
2672         return priv->remote_contact;
2673 }
2674
2675 GtkWidget *
2676 empathy_chat_get_contact_menu (EmpathyChat *chat)
2677 {
2678         EmpathyChatPriv *priv = GET_PRIV (chat);
2679         GtkWidget       *menu = NULL;
2680
2681         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2682
2683         if (priv->remote_contact) {
2684                 menu = empathy_contact_menu_new (priv->remote_contact,
2685                                                  EMPATHY_CONTACT_FEATURE_CALL |
2686                                                  EMPATHY_CONTACT_FEATURE_LOG |
2687                                                  EMPATHY_CONTACT_FEATURE_INFO);
2688         }
2689         else if (priv->contact_list_view) {
2690                 EmpathyContactListView *view;
2691
2692                 view = EMPATHY_CONTACT_LIST_VIEW (priv->contact_list_view);
2693                 menu = empathy_contact_list_view_get_contact_menu (view);
2694         }
2695
2696         return menu;
2697 }
2698
2699 void
2700 empathy_chat_clear (EmpathyChat *chat)
2701 {
2702         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2703
2704         empathy_chat_view_clear (chat->view);
2705 }
2706
2707 void
2708 empathy_chat_scroll_down (EmpathyChat *chat)
2709 {
2710         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2711
2712         empathy_chat_view_scroll_down (chat->view);
2713 }
2714
2715 void
2716 empathy_chat_cut (EmpathyChat *chat)
2717 {
2718         GtkTextBuffer *buffer;
2719
2720         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2721
2722         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2723         if (gtk_text_buffer_get_has_selection (buffer)) {
2724                 GtkClipboard *clipboard;
2725
2726                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2727
2728                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
2729         }
2730 }
2731
2732 void
2733 empathy_chat_copy (EmpathyChat *chat)
2734 {
2735         GtkTextBuffer *buffer;
2736
2737         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2738
2739         if (empathy_chat_view_get_has_selection (chat->view)) {
2740                 empathy_chat_view_copy_clipboard (chat->view);
2741                 return;
2742         }
2743
2744         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2745         if (gtk_text_buffer_get_has_selection (buffer)) {
2746                 GtkClipboard *clipboard;
2747
2748                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2749
2750                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2751         }
2752 }
2753
2754 void
2755 empathy_chat_paste (EmpathyChat *chat)
2756 {
2757         GtkTextBuffer *buffer;
2758         GtkClipboard  *clipboard;
2759         EmpathyChatPriv *priv;
2760
2761         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2762
2763         priv = GET_PRIV (chat);
2764
2765         if (priv->tp_chat == NULL ||
2766             !GTK_WIDGET_IS_SENSITIVE (chat->input_text_view))
2767                 return;
2768
2769         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2770         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2771
2772         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
2773 }
2774
2775 void
2776 empathy_chat_correct_word (EmpathyChat  *chat,
2777                           GtkTextIter *start,
2778                           GtkTextIter *end,
2779                           const gchar *new_word)
2780 {
2781         GtkTextBuffer *buffer;
2782
2783         g_return_if_fail (chat != NULL);
2784         g_return_if_fail (new_word != NULL);
2785
2786         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2787
2788         gtk_text_buffer_delete (buffer, start, end);
2789         gtk_text_buffer_insert (buffer, start,
2790                                 new_word,
2791                                 -1);
2792 }
2793
2794 gboolean
2795 empathy_chat_is_room (EmpathyChat *chat)
2796 {
2797         EmpathyChatPriv *priv = GET_PRIV (chat);
2798
2799         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
2800
2801         return (priv->handle_type == TP_HANDLE_TYPE_ROOM);
2802 }
2803