]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat.c
dc332c6d9f56dce2f8b3f653d9070495616b0ce0
[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_me (EmpathyChat *chat,
716                   GStrv        strv)
717 {
718         EmpathyChatPriv *priv = GET_PRIV (chat);
719         EmpathyMessage *message;
720
721         message = empathy_message_new (strv[1]);
722         empathy_message_set_tptype (message, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
723         empathy_tp_chat_send (priv->tp_chat, message);
724         g_object_unref (message);
725 }
726
727 static void
728 chat_command_say (EmpathyChat *chat,
729                   GStrv        strv)
730 {
731         EmpathyChatPriv *priv = GET_PRIV (chat);
732         EmpathyMessage *message;
733
734         message = empathy_message_new (strv[1]);
735         empathy_tp_chat_send (priv->tp_chat, message);
736         g_object_unref (message);
737 }
738
739 static void chat_command_help (EmpathyChat *chat, GStrv strv);
740
741 typedef void (*ChatCommandFunc) (EmpathyChat *chat, GStrv strv);
742
743 typedef struct {
744         const gchar *prefix;
745         guint min_parts;
746         guint max_parts;
747         ChatCommandFunc func;
748         const gchar *help;
749 } ChatCommandItem;
750
751 static ChatCommandItem commands[] = {
752         {"clear", 1, 1, chat_command_clear,
753          N_("/clear, clear all messages from the current conversation")},
754
755         {"topic", 2, 2, chat_command_topic,
756          N_("/topic <topic>, set the topic of the current conversation")},
757
758         {"join", 2, 2, chat_command_join,
759          N_("/join <chatroom id>, join a new chatroom")},
760
761         {"j", 2, 2, chat_command_join,
762          N_("/j <chatroom id>, join a new chatroom")},
763
764         {"query", 2, 3, chat_command_query,
765          N_("/query <contact id> [<message>], open a private chat")},
766
767         {"msg", 3, 3, chat_command_msg,
768          N_("/msg <contact id> <message>, open a private chat")},
769
770         {"me", 2, 2, chat_command_me,
771          N_("/me <message>, send an ACTION message to the current conversation")},
772
773         {"say", 2, 2, chat_command_say,
774          N_("/say <message>, send <message> to the current conversation. "
775             "This is used to send a message starting with a '/'. For example: "
776             "\"/say /join is used to join a new chatroom\"")},
777
778         {"help", 1, 2, chat_command_help,
779          N_("/help [<command>], show all supported commands. "
780             "If <command> is defined, show its usage.")},
781 };
782
783 static void
784 chat_command_show_help (EmpathyChat     *chat,
785                         ChatCommandItem *item)
786 {
787         gchar *str;
788
789         str = g_strdup_printf (_("Usage: %s"), _(item->help));
790         empathy_chat_view_append_event (chat->view, str);
791         g_free (str);
792 }
793
794 static void
795 chat_command_help (EmpathyChat *chat,
796                    GStrv        strv)
797 {
798         guint i;
799
800         /* If <command> part is not defined,
801          * strv[1] will be the terminal NULL */
802         if (strv[1] == NULL) {
803                 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
804                         empathy_chat_view_append_event (chat->view,
805                                 _(commands[i].help));
806                 }
807                 return;
808         }
809
810         for (i = 0; i < G_N_ELEMENTS (commands); i++) {
811                 if (g_ascii_strcasecmp (strv[1], commands[i].prefix) == 0) {
812                         chat_command_show_help (chat, &commands[i]);
813                         return;
814                 }
815         }
816
817         empathy_chat_view_append_event (chat->view,
818                 _("Unknown command"));
819 }
820
821 static GStrv
822 chat_command_parse (const gchar *text, guint max_parts)
823 {
824         GPtrArray *array;
825         gchar *item;
826
827         DEBUG ("Parse command, parts=%d text=\"%s\":", max_parts, text);
828
829         array = g_ptr_array_sized_new (max_parts + 1);
830         while (max_parts > 1) {
831                 const gchar *end;
832
833                 /* Skip white spaces */
834                 while (g_ascii_isspace (*text)) {
835                         text++;
836                 }
837
838                 /* Search the end of this part, until first space. */
839                 for (end = text; *end != '\0' && !g_ascii_isspace (*end); end++)
840                         /* Do nothing */;
841                 if (*end == '\0') {
842                         break;
843                 }
844
845                 item = g_strndup (text, end - text);
846                 g_ptr_array_add (array, item);
847                 DEBUG ("\tITEM: \"%s\"", item);
848
849                 text = end;
850                 max_parts--;
851         }
852
853         /* Append last part if not empty */
854         item = g_strstrip (g_strdup (text));
855         if (!EMP_STR_EMPTY (item)) {
856                 g_ptr_array_add (array, item);
857                 DEBUG ("\tITEM: \"%s\"", item);
858         } else {
859                 g_free (item);
860         }
861
862         /* Make the array NULL-terminated */
863         g_ptr_array_add (array, NULL);
864
865         return (GStrv) g_ptr_array_free (array, FALSE);
866 }
867
868 static gboolean
869 has_prefix_case (const gchar *s,
870                   const gchar *prefix)
871 {
872         return g_ascii_strncasecmp (s, prefix, strlen (prefix)) == 0;
873 }
874
875 static void
876 chat_send (EmpathyChat  *chat,
877            const gchar *msg)
878 {
879         EmpathyChatPriv *priv;
880         EmpathyMessage  *message;
881         guint            i;
882
883         if (EMP_STR_EMPTY (msg)) {
884                 return;
885         }
886
887         priv = GET_PRIV (chat);
888
889         chat_input_history_add (chat, msg, FALSE);
890
891         if (msg[0] == '/') {
892                 gboolean second_slash = FALSE;
893                 const gchar *iter = msg + 1;
894
895                 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
896                         GStrv strv;
897                         guint strv_len;
898                         gchar c;
899
900                         if (!has_prefix_case (msg + 1, commands[i].prefix)) {
901                                 continue;
902                         }
903                         c = *(msg + 1 + strlen (commands[i].prefix));
904                         if (c != '\0' && !g_ascii_isspace (c)) {
905                                 continue;
906                         }
907
908                         /* We can't use g_strsplit here because it does
909                          * not deal correctly if we have more than one space
910                          * between args */
911                         strv = chat_command_parse (msg + 1, commands[i].max_parts);
912
913                         strv_len = g_strv_length (strv);
914                         if (strv_len < commands[i].min_parts ||
915                             strv_len > commands[i].max_parts) {
916                                 chat_command_show_help (chat, &commands[i]);
917                                 g_strfreev (strv);
918                                 return;
919                         }
920
921                         commands[i].func (chat, strv);
922                         g_strfreev (strv);
923                         return;
924                 }
925
926                 /* Also allow messages with two slashes before the
927                  * first space, so it is possible to send a /unix/path.
928                  * This heuristic is kind of crap. */
929                 while (*iter != '\0' && !g_ascii_isspace (*iter)) {
930                         if (*iter == '/') {
931                                 second_slash = TRUE;
932                                 break;
933                         }
934                         iter++;
935                 }
936
937                 if (!second_slash) {
938                         empathy_chat_view_append_event (chat->view,
939                                 _("Unknown command, see /help for the available"
940                                   " commands"));
941                         return;
942                 }
943         }
944
945         message = empathy_message_new (msg);
946         empathy_tp_chat_send (priv->tp_chat, message);
947         g_object_unref (message);
948 }
949
950 static void
951 chat_input_text_view_send (EmpathyChat *chat)
952 {
953         EmpathyChatPriv *priv;
954         GtkTextBuffer  *buffer;
955         GtkTextIter     start, end;
956         gchar          *msg;
957
958         priv = GET_PRIV (chat);
959
960         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
961
962         gtk_text_buffer_get_bounds (buffer, &start, &end);
963         msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
964
965         /* clear the input field */
966         gtk_text_buffer_set_text (buffer, "", -1);
967         /* delete input history modifications */
968         chat_input_history_revert (chat);
969
970         chat_send (chat, msg);
971         g_free (msg);
972 }
973
974 static void
975 chat_state_changed_cb (EmpathyTpChat      *tp_chat,
976                        EmpathyContact     *contact,
977                        TpChannelChatState  state,
978                        EmpathyChat        *chat)
979 {
980         EmpathyChatPriv *priv;
981         GList          *l;
982         gboolean        was_composing;
983
984         priv = GET_PRIV (chat);
985
986         if (empathy_contact_is_user (contact)) {
987                 /* We don't care about our own chat state */
988                 return;
989         }
990
991         was_composing = (priv->compositors != NULL);
992
993         /* Find the contact in the list. After that l is the list elem or NULL */
994         for (l = priv->compositors; l; l = l->next) {
995                 if (contact == l->data) {
996                         break;
997                 }
998         }
999
1000         switch (state) {
1001         case TP_CHANNEL_CHAT_STATE_GONE:
1002         case TP_CHANNEL_CHAT_STATE_INACTIVE:
1003         case TP_CHANNEL_CHAT_STATE_PAUSED:
1004         case TP_CHANNEL_CHAT_STATE_ACTIVE:
1005                 /* Contact is not composing */
1006                 if (l) {
1007                         priv->compositors = g_list_remove_link (priv->compositors, l);
1008                         g_object_unref (l->data);
1009                         g_list_free1 (l);
1010                 }
1011                 break;
1012         case TP_CHANNEL_CHAT_STATE_COMPOSING:
1013                 /* Contact is composing */
1014                 if (!l) {
1015                         priv->compositors = g_list_prepend (priv->compositors,
1016                                                             g_object_ref (contact));
1017                 }
1018                 break;
1019         default:
1020                 g_assert_not_reached ();
1021         }
1022
1023         DEBUG ("Was composing: %s now composing: %s",
1024                 was_composing ? "yes" : "no",
1025                 priv->compositors ? "yes" : "no");
1026
1027         if ((was_composing && !priv->compositors) ||
1028             (!was_composing && priv->compositors)) {
1029                 /* Composing state changed */
1030                 g_signal_emit (chat, signals[COMPOSING], 0,
1031                                priv->compositors != NULL);
1032         }
1033 }
1034
1035 static void
1036 chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
1037 {
1038         EmpathyChatPriv *priv = GET_PRIV (chat);
1039         EmpathyContact  *sender;
1040
1041         sender = empathy_message_get_sender (message);
1042
1043         DEBUG ("Appending new message from %s (%d)",
1044                 empathy_contact_get_name (sender),
1045                 empathy_contact_get_handle (sender));
1046
1047         empathy_chat_view_append_message (chat->view, message);
1048
1049         /* We received a message so the contact is no longer composing */
1050         chat_state_changed_cb (priv->tp_chat, sender,
1051                                TP_CHANNEL_CHAT_STATE_ACTIVE,
1052                                chat);
1053
1054         g_signal_emit (chat, signals[NEW_MESSAGE], 0, message);
1055 }
1056
1057 static void
1058 chat_message_received_cb (EmpathyTpChat  *tp_chat,
1059                           EmpathyMessage *message,
1060                           EmpathyChat    *chat)
1061 {
1062         chat_message_received (chat, message);
1063         empathy_tp_chat_acknowledge_message (tp_chat, message);
1064 }
1065
1066 static void
1067 chat_send_error_cb (EmpathyTpChat          *tp_chat,
1068                     const gchar            *message_body,
1069                     TpChannelTextSendError  error_code,
1070                     EmpathyChat            *chat)
1071 {
1072         const gchar *error;
1073         gchar       *str;
1074
1075         switch (error_code) {
1076         case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
1077                 error = _("offline");
1078                 break;
1079         case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
1080                 error = _("invalid contact");
1081                 break;
1082         case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
1083                 error = _("permission denied");
1084                 break;
1085         case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
1086                 error = _("too long message");
1087                 break;
1088         case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
1089                 error = _("not implemented");
1090                 break;
1091         default:
1092                 error = _("unknown");
1093                 break;
1094         }
1095
1096         str = g_strdup_printf (_("Error sending message '%s': %s"),
1097                                message_body,
1098                                error);
1099         empathy_chat_view_append_event (chat->view, str);
1100         g_free (str);
1101 }
1102
1103 static void
1104 chat_property_changed_cb (EmpathyTpChat *tp_chat,
1105                           const gchar   *name,
1106                           GValue        *value,
1107                           EmpathyChat   *chat)
1108 {
1109         EmpathyChatPriv *priv = GET_PRIV (chat);
1110
1111         if (!tp_strdiff (name, "subject")) {
1112                 g_free (priv->subject);
1113                 priv->subject = g_value_dup_string (value);
1114                 g_object_notify (G_OBJECT (chat), "subject");
1115
1116                 if (EMP_STR_EMPTY (priv->subject)) {
1117                         gtk_widget_hide (priv->hbox_topic);
1118                 } else {
1119                         gtk_label_set_text (GTK_LABEL (priv->label_topic), priv->subject);
1120                         gtk_widget_show (priv->hbox_topic);
1121                 }
1122                 if (priv->block_events_timeout_id == 0) {
1123                         gchar *str;
1124
1125                         if (!EMP_STR_EMPTY (priv->subject)) {
1126                                 str = g_strdup_printf (_("Topic set to: %s"), priv->subject);
1127                         } else {
1128                                 str = g_strdup (_("No topic defined"));
1129                         }
1130                         empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str);
1131                         g_free (str);
1132                 }
1133         }
1134         else if (!tp_strdiff (name, "name")) {
1135                 g_free (priv->name);
1136                 priv->name = g_value_dup_string (value);
1137                 g_object_notify (G_OBJECT (chat), "name");
1138         }
1139 }
1140
1141 static void
1142 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
1143                                    EmpathyChat    *chat)
1144 {
1145         EmpathyChatPriv *priv;
1146         GtkTextIter     start, end;
1147         gchar          *str;
1148         gboolean        spell_checker = FALSE;
1149
1150         priv = GET_PRIV (chat);
1151
1152         if (gtk_text_buffer_get_char_count (buffer) == 0) {
1153                 chat_composing_stop (chat);
1154         } else {
1155                 chat_composing_start (chat);
1156         }
1157
1158         empathy_conf_get_bool (empathy_conf_get (),
1159                            EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
1160                            &spell_checker);
1161
1162         gtk_text_buffer_get_start_iter (buffer, &start);
1163
1164         if (!spell_checker) {
1165                 gtk_text_buffer_get_end_iter (buffer, &end);
1166                 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
1167                 return;
1168         }
1169
1170         if (!empathy_spell_supported ()) {
1171                 return;
1172         }
1173
1174         /* NOTE: this is really inefficient, we shouldn't have to
1175            reiterate the whole buffer each time and check each work
1176            every time. */
1177         while (TRUE) {
1178                 gboolean correct = FALSE;
1179
1180                 /* if at start */
1181                 if (gtk_text_iter_is_start (&start)) {
1182                         end = start;
1183
1184                         if (!gtk_text_iter_forward_word_end (&end)) {
1185                                 /* no whole word yet */
1186                                 break;
1187                         }
1188                 } else {
1189                         if (!gtk_text_iter_forward_word_end (&end)) {
1190                                 /* must be the end of the buffer */
1191                                 break;
1192                         }
1193
1194                         start = end;
1195                         gtk_text_iter_backward_word_start (&start);
1196                 }
1197
1198                 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1199
1200                 /* spell check string if not a command */
1201                 if (str[0] != '/') {
1202                         correct = empathy_spell_check (str);
1203                 } else {
1204                         correct = TRUE;
1205                 }
1206
1207                 if (!correct) {
1208                         gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
1209                 } else {
1210                         gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
1211                 }
1212
1213                 g_free (str);
1214
1215                 /* set start iter to the end iters position */
1216                 start = end;
1217         }
1218 }
1219
1220 static gboolean
1221 chat_input_key_press_event_cb (GtkWidget   *widget,
1222                                GdkEventKey *event,
1223                                EmpathyChat *chat)
1224 {
1225         EmpathyChatPriv *priv;
1226         GtkAdjustment  *adj;
1227         gdouble         val;
1228         GtkWidget      *text_view_sw;
1229
1230         priv = GET_PRIV (chat);
1231
1232         /* Catch ctrl+up/down so we can traverse messages we sent */
1233         if ((event->state & GDK_CONTROL_MASK) &&
1234             (event->keyval == GDK_Up ||
1235              event->keyval == GDK_Down)) {
1236                 GtkTextBuffer *buffer;
1237                 const gchar   *str;
1238
1239                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1240                 chat_input_history_update (chat, buffer);
1241
1242                 if (event->keyval == GDK_Up) {
1243                         str = chat_input_history_get_next (chat);
1244                 } else {
1245                         str = chat_input_history_get_prev (chat);
1246                 }
1247
1248                 g_signal_handlers_block_by_func (buffer,
1249                                                  chat_input_text_buffer_changed_cb,
1250                                                  chat);
1251                 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
1252                 g_signal_handlers_unblock_by_func (buffer,
1253                                                    chat_input_text_buffer_changed_cb,
1254                                                    chat);
1255
1256                 return TRUE;
1257         }
1258
1259         /* Catch enter but not ctrl/shift-enter */
1260         if (IS_ENTER (event->keyval) &&
1261             !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
1262                 GtkTextView *view;
1263
1264                 /* This is to make sure that kinput2 gets the enter. And if
1265                  * it's handled there we shouldn't send on it. This is because
1266                  * kinput2 uses Enter to commit letters. See:
1267                  * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
1268                  */
1269
1270                 view = GTK_TEXT_VIEW (chat->input_text_view);
1271                 if (gtk_im_context_filter_keypress (view->im_context, event)) {
1272                         GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
1273                         return TRUE;
1274                 }
1275
1276                 chat_input_text_view_send (chat);
1277                 return TRUE;
1278         }
1279
1280         text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
1281
1282         if (IS_ENTER (event->keyval) &&
1283             (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
1284                 /* Newline for shift/control-enter. */
1285                 return FALSE;
1286         }
1287         if (!(event->state & GDK_CONTROL_MASK) &&
1288             event->keyval == GDK_Page_Up) {
1289                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
1290                 gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) - gtk_adjustment_get_page_size (adj));
1291                 return TRUE;
1292         }
1293         if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
1294             event->keyval == GDK_Page_Down) {
1295                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
1296                 val = MIN (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj),
1297                            gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
1298                 gtk_adjustment_set_value (adj, val);
1299                 return TRUE;
1300         }
1301         if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) &&
1302             event->keyval == GDK_Tab) {
1303                 GtkTextBuffer *buffer;
1304                 GtkTextIter    start, current;
1305                 gchar         *nick, *completed;
1306                 GList         *list, *completed_list;
1307                 gboolean       is_start_of_buffer;
1308
1309                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (EMPATHY_CHAT (chat)->input_text_view));
1310                 gtk_text_buffer_get_iter_at_mark (buffer, &current, gtk_text_buffer_get_insert (buffer));
1311
1312                 /* Get the start of the nick to complete. */
1313                 gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
1314                 gtk_text_iter_backward_word_start (&start);
1315                 is_start_of_buffer = gtk_text_iter_is_start (&start);
1316
1317                 list = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (priv->tp_chat));
1318                 g_completion_add_items (priv->completion, list);
1319
1320                 nick = gtk_text_buffer_get_text (buffer, &start, &current, FALSE);
1321                 completed_list = g_completion_complete (priv->completion,
1322                                                         nick,
1323                                                         &completed);
1324
1325                 g_free (nick);
1326
1327                 if (completed) {
1328                         guint        len;
1329                         const gchar *text;
1330                         gchar       *complete_char = NULL;
1331
1332                         gtk_text_buffer_delete (buffer, &start, &current);
1333
1334                         len = g_list_length (completed_list);
1335
1336                         if (len == 1) {
1337                                 /* If we only have one hit, use that text
1338                                  * instead of the text in completed since the
1339                                  * completed text will use the typed string
1340                                  * which might be cased all wrong.
1341                                  * Fixes #120876
1342                                  * */
1343                                 text = empathy_contact_get_name (completed_list->data);
1344                         } else {
1345                                 text = completed;
1346                         }
1347
1348                         gtk_text_buffer_insert_at_cursor (buffer, text, strlen (text));
1349
1350                         if (len == 1 && is_start_of_buffer &&
1351                             empathy_conf_get_string (empathy_conf_get (),
1352                                                      EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR,
1353                                                      &complete_char) &&
1354                             complete_char != NULL) {
1355                                 gtk_text_buffer_insert_at_cursor (buffer,
1356                                                                   complete_char,
1357                                                                   strlen (complete_char));
1358                                 gtk_text_buffer_insert_at_cursor (buffer, " ", 1);
1359                                 g_free (complete_char);
1360                         }
1361
1362                         g_free (completed);
1363                 }
1364
1365                 g_completion_clear_items (priv->completion);
1366
1367                 g_list_foreach (list, (GFunc) g_object_unref, NULL);
1368                 g_list_free (list);
1369
1370                 return TRUE;
1371         }
1372
1373         return FALSE;
1374 }
1375
1376 static gboolean
1377 chat_text_view_focus_in_event_cb (GtkWidget  *widget,
1378                                   GdkEvent   *event,
1379                                   EmpathyChat *chat)
1380 {
1381         gtk_widget_grab_focus (chat->input_text_view);
1382
1383         return TRUE;
1384 }
1385
1386 static gboolean
1387 chat_input_set_size_request_idle (gpointer sw)
1388 {
1389         gtk_widget_set_size_request (sw, -1, MAX_INPUT_HEIGHT);
1390
1391         return FALSE;
1392 }
1393
1394 static void
1395 chat_input_size_request_cb (GtkWidget      *widget,
1396                             GtkRequisition *requisition,
1397                             EmpathyChat    *chat)
1398 {
1399         EmpathyChatPriv *priv = GET_PRIV (chat);
1400         GtkWidget       *sw;
1401
1402         sw = gtk_widget_get_parent (widget);
1403         if (requisition->height >= MAX_INPUT_HEIGHT && !priv->has_input_vscroll) {
1404                 g_idle_add (chat_input_set_size_request_idle, sw);
1405                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1406                                                 GTK_POLICY_NEVER,
1407                                                 GTK_POLICY_ALWAYS);
1408                 priv->has_input_vscroll = TRUE;
1409         }
1410
1411         if (requisition->height < MAX_INPUT_HEIGHT && priv->has_input_vscroll) {
1412                 gtk_widget_set_size_request (sw, -1, -1);
1413                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1414                                                 GTK_POLICY_NEVER,
1415                                                 GTK_POLICY_NEVER);
1416                 priv->has_input_vscroll = FALSE;
1417         }
1418 }
1419
1420 static void
1421 chat_input_realize_cb (GtkWidget   *widget,
1422                        EmpathyChat *chat)
1423 {
1424         DEBUG ("Setting focus to the input text view");
1425         if (gtk_widget_is_sensitive (widget)) {
1426                 gtk_widget_grab_focus (widget);
1427         }
1428 }
1429
1430 static void
1431 chat_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1432                                 EmpathySmiley        *smiley,
1433                                 gpointer              user_data)
1434 {
1435         EmpathyChat   *chat = EMPATHY_CHAT (user_data);
1436         GtkTextBuffer *buffer;
1437         GtkTextIter    iter;
1438
1439         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1440
1441         gtk_text_buffer_get_end_iter (buffer, &iter);
1442         gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1443
1444         gtk_text_buffer_get_end_iter (buffer, &iter);
1445         gtk_text_buffer_insert (buffer, &iter, " ", -1);
1446 }
1447
1448 typedef struct {
1449         EmpathyChat  *chat;
1450         gchar       *word;
1451
1452         GtkTextIter  start;
1453         GtkTextIter  end;
1454 } EmpathyChatSpell;
1455
1456 static EmpathyChatSpell *
1457 chat_spell_new (EmpathyChat  *chat,
1458                 const gchar *word,
1459                 GtkTextIter  start,
1460                 GtkTextIter  end)
1461 {
1462         EmpathyChatSpell *chat_spell;
1463
1464         chat_spell = g_slice_new0 (EmpathyChatSpell);
1465
1466         chat_spell->chat = g_object_ref (chat);
1467         chat_spell->word = g_strdup (word);
1468         chat_spell->start = start;
1469         chat_spell->end = end;
1470
1471         return chat_spell;
1472 }
1473
1474 static void
1475 chat_spell_free (EmpathyChatSpell *chat_spell)
1476 {
1477         g_object_unref (chat_spell->chat);
1478         g_free (chat_spell->word);
1479         g_slice_free (EmpathyChatSpell, chat_spell);
1480 }
1481
1482 static void
1483 chat_spelling_menu_activate_cb (GtkMenuItem     *menu_item,
1484                                                 EmpathyChatSpell *chat_spell)
1485 {
1486     empathy_chat_correct_word (chat_spell->chat,
1487                                &(chat_spell->start),
1488                                &(chat_spell->end),
1489                                gtk_menu_item_get_label (menu_item));
1490 }
1491
1492 static GtkWidget *
1493 chat_spelling_build_menu (EmpathyChatSpell *chat_spell)
1494 {
1495     GtkWidget *menu, *menu_item;
1496     GList     *suggestions, *l;
1497
1498     menu = gtk_menu_new ();
1499     suggestions = empathy_spell_get_suggestions (chat_spell->word);
1500     if (suggestions == NULL) {
1501         menu_item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
1502         gtk_widget_set_sensitive (menu_item, FALSE);
1503         gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1504     } else {
1505         for (l = suggestions; l; l = l->next) {
1506             menu_item = gtk_menu_item_new_with_label (l->data);
1507             g_signal_connect (G_OBJECT (menu_item),
1508                           "activate",
1509                           G_CALLBACK (chat_spelling_menu_activate_cb),
1510                           chat_spell);
1511             gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1512         }
1513     }
1514     empathy_spell_free_suggestions (suggestions);
1515
1516     gtk_widget_show_all (menu);
1517
1518     return menu;
1519 }
1520
1521 static void
1522 chat_text_send_cb (GtkMenuItem *menuitem,
1523                    EmpathyChat *chat)
1524 {
1525         chat_input_text_view_send (chat);
1526 }
1527
1528 static void
1529 chat_input_populate_popup_cb (GtkTextView *view,
1530                               GtkMenu     *menu,
1531                               EmpathyChat *chat)
1532 {
1533         EmpathyChatPriv      *priv;
1534         GtkTextBuffer        *buffer;
1535         GtkTextTagTable      *table;
1536         GtkTextTag           *tag;
1537         gint                  x, y;
1538         GtkTextIter           iter, start, end;
1539         GtkWidget            *item;
1540         gchar                *str = NULL;
1541         EmpathyChatSpell     *chat_spell;
1542         GtkWidget            *spell_menu;
1543         EmpathySmileyManager *smiley_manager;
1544         GtkWidget            *smiley_menu;
1545         GtkWidget            *image;
1546
1547         priv = GET_PRIV (chat);
1548         buffer = gtk_text_view_get_buffer (view);
1549
1550         /* Add the emoticon menu. */
1551         item = gtk_separator_menu_item_new ();
1552         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1553         gtk_widget_show (item);
1554
1555         item = gtk_image_menu_item_new_with_mnemonic (_("Insert Smiley"));
1556         image = gtk_image_new_from_icon_name ("face-smile",
1557                                               GTK_ICON_SIZE_MENU);
1558         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1559         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1560         gtk_widget_show (item);
1561
1562         smiley_manager = empathy_smiley_manager_dup_singleton ();
1563         smiley_menu = empathy_smiley_menu_new (smiley_manager,
1564                                                chat_insert_smiley_activate_cb,
1565                                                chat);
1566         gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
1567         g_object_unref (smiley_manager);
1568
1569         /* Add the Send menu item. */
1570         gtk_text_buffer_get_bounds (buffer, &start, &end);
1571         str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1572         if (!EMP_STR_EMPTY (str)) {
1573                 item = gtk_menu_item_new_with_mnemonic (_("_Send"));
1574                 g_signal_connect (G_OBJECT (item), "activate",
1575                                   G_CALLBACK (chat_text_send_cb), chat);
1576                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1577                 gtk_widget_show (item);
1578         }
1579         str = NULL;
1580
1581         /* Add the spell check menu item. */
1582         table = gtk_text_buffer_get_tag_table (buffer);
1583         tag = gtk_text_tag_table_lookup (table, "misspelled");
1584         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
1585         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
1586                                                GTK_TEXT_WINDOW_WIDGET,
1587                                                x, y,
1588                                                &x, &y);
1589         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
1590         start = end = iter;
1591         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
1592             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
1593
1594                 str = gtk_text_buffer_get_text (buffer,
1595                                                 &start, &end, FALSE);
1596         }
1597         if (!EMP_STR_EMPTY (str)) {
1598                 chat_spell = chat_spell_new (chat, str, start, end);
1599                 g_object_set_data_full (G_OBJECT (menu),
1600                                         "chat_spell", chat_spell,
1601                                         (GDestroyNotify) chat_spell_free);
1602
1603                 item = gtk_separator_menu_item_new ();
1604                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1605                 gtk_widget_show (item);
1606
1607                 item = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
1608                 image = gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK,
1609                                                       GTK_ICON_SIZE_MENU);
1610                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1611
1612                 spell_menu = chat_spelling_build_menu (chat_spell);
1613                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), spell_menu);
1614
1615                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1616                 gtk_widget_show (item);
1617         }
1618 }
1619
1620 static gboolean
1621 chat_log_filter (EmpathyMessage *message,
1622                  gpointer user_data)
1623 {
1624         EmpathyChat *chat = (EmpathyChat *) user_data;
1625         EmpathyChatPriv *priv = GET_PRIV (chat);
1626         const GList *pending;
1627
1628         pending = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1629
1630         for (; pending; pending = g_list_next (pending)) {
1631                 if (empathy_message_equal (message, pending->data)) {
1632                         return FALSE;
1633                 }
1634         }
1635
1636         return TRUE;
1637 }
1638
1639 static void
1640 chat_add_logs (EmpathyChat *chat)
1641 {
1642         EmpathyChatPriv *priv = GET_PRIV (chat);
1643         gboolean         is_chatroom;
1644         GList           *messages, *l;
1645
1646         if (!priv->id) {
1647                 return;
1648         }
1649
1650         /* Turn off scrolling temporarily */
1651         empathy_chat_view_scroll (chat->view, FALSE);
1652
1653         /* Add messages from last conversation */
1654         is_chatroom = priv->handle_type == TP_HANDLE_TYPE_ROOM;
1655
1656         messages = empathy_log_manager_get_filtered_messages (priv->log_manager,
1657                                                               priv->account,
1658                                                               priv->id,
1659                                                               is_chatroom,
1660                                                               5,
1661                                                               chat_log_filter,
1662                                                               chat);
1663
1664         for (l = messages; l; l = g_list_next (l)) {
1665                 empathy_chat_view_append_message (chat->view, l->data);
1666                 g_object_unref (l->data);
1667         }
1668
1669         g_list_free (messages);
1670
1671         /* Turn back on scrolling */
1672         empathy_chat_view_scroll (chat->view, TRUE);
1673 }
1674
1675 static gint
1676 chat_contacts_completion_func (const gchar *s1,
1677                                const gchar *s2,
1678                                gsize        n)
1679 {
1680         gchar *tmp, *nick1, *nick2;
1681         gint   ret;
1682
1683         if (s1 == s2) {
1684                 return 0;
1685         }
1686         if (!s1 || !s2) {
1687                 return s1 ? -1 : +1;
1688         }
1689
1690         tmp = g_utf8_normalize (s1, -1, G_NORMALIZE_DEFAULT);
1691         nick1 = g_utf8_casefold (tmp, -1);
1692         g_free (tmp);
1693
1694         tmp = g_utf8_normalize (s2, -1, G_NORMALIZE_DEFAULT);
1695         nick2 = g_utf8_casefold (tmp, -1);
1696         g_free (tmp);
1697
1698         ret = strncmp (nick1, nick2, n);
1699
1700         g_free (nick1);
1701         g_free (nick2);
1702
1703         return ret;
1704 }
1705
1706 static gchar *
1707 build_part_message (guint           reason,
1708                     const gchar    *name,
1709                     EmpathyContact *actor,
1710                     const gchar    *message)
1711 {
1712         GString *s = g_string_new ("");
1713         const gchar *actor_name = NULL;
1714
1715         if (actor != NULL) {
1716                 actor_name = empathy_contact_get_name (actor);
1717         }
1718
1719         /* Having an actor only really makes sense for a few actions... */
1720         switch (reason) {
1721         case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
1722                 g_string_append_printf (s, _("%s has disconnected"), name);
1723                 break;
1724         case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED:
1725                 if (actor_name != NULL) {
1726                         /* translators: reverse the order of these arguments
1727                          * if the kicked should come before the kicker in your locale.
1728                          */
1729                         g_string_append_printf (s, _("%1$s was kicked by %2$s"),
1730                                 name, actor_name);
1731                 } else {
1732                         g_string_append_printf (s, _("%s was kicked"), name);
1733                 }
1734                 break;
1735         case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
1736                 if (actor_name != NULL) {
1737                         /* translators: reverse the order of these arguments
1738                          * if the banned should come before the banner in your locale.
1739                          */
1740                         g_string_append_printf (s, _("%1$s was banned by %2$s"),
1741                                 name, actor_name);
1742                 } else {
1743                         g_string_append_printf (s, _("%s was banned"), name);
1744                 }
1745                 break;
1746         default:
1747                 g_string_append_printf (s, _("%s has left the room"), name);
1748         }
1749
1750         if (!EMP_STR_EMPTY (message)) {
1751                 /* Note to translators: this string is appended to
1752                  * notifications like "foo has left the room", with the message
1753                  * given by the user living the room. If this poses a problem,
1754                  * please let us know. :-)
1755                  */
1756                 g_string_append_printf (s, _(" (%s)"), message);
1757         }
1758
1759         return g_string_free (s, FALSE);
1760 }
1761
1762 static void
1763 chat_members_changed_cb (EmpathyTpChat  *tp_chat,
1764                          EmpathyContact *contact,
1765                          EmpathyContact *actor,
1766                          guint           reason,
1767                          gchar          *message,
1768                          gboolean        is_member,
1769                          EmpathyChat    *chat)
1770 {
1771         EmpathyChatPriv *priv = GET_PRIV (chat);
1772         const gchar *name = empathy_contact_get_name (contact);
1773         gchar *str;
1774
1775         if (priv->block_events_timeout_id != 0)
1776                 return;
1777
1778         if (is_member) {
1779                 str = g_strdup_printf (_("%s has joined the room"),
1780                                        name);
1781         } else {
1782                 str = build_part_message (reason, name, actor, message);
1783         }
1784
1785         empathy_chat_view_append_event (chat->view, str);
1786         g_free (str);
1787 }
1788
1789 static gboolean
1790 chat_reset_size_request (gpointer widget)
1791 {
1792         gtk_widget_set_size_request (widget, -1, -1);
1793
1794         return FALSE;
1795 }
1796
1797 static void
1798 chat_update_contacts_visibility (EmpathyChat *chat)
1799 {
1800         EmpathyChatPriv *priv = GET_PRIV (chat);
1801         gboolean show;
1802
1803         show = priv->remote_contact == NULL && priv->show_contacts;
1804
1805         if (!priv->scrolled_window_contacts) {
1806                 return;
1807         }
1808
1809         if (show && priv->contact_list_view == NULL) {
1810                 EmpathyContactListStore *store;
1811                 gint                     min_width;
1812
1813                 /* We are adding the contact list to the chat, we don't want the
1814                  * chat view to become too small. If the chat view is already
1815                  * smaller than 250 make sure that size won't change. If the
1816                  * chat view is bigger the contact list will take some space on
1817                  * it but we make sure the chat view don't become smaller than
1818                  * 250. Relax the size request once the resize is done */
1819                 min_width = MIN (priv->vbox_left->allocation.width, 250);
1820                 gtk_widget_set_size_request (priv->vbox_left, min_width, -1);
1821                 g_idle_add (chat_reset_size_request, priv->vbox_left);
1822
1823                 if (priv->contacts_width > 0) {
1824                         gtk_paned_set_position (GTK_PANED (priv->hpaned),
1825                                                 priv->contacts_width);
1826                 }
1827
1828                 store = empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
1829                 priv->contact_list_view = GTK_WIDGET (empathy_contact_list_view_new (store,
1830                         EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP,
1831                         EMPATHY_CONTACT_FEATURE_CHAT |
1832                         EMPATHY_CONTACT_FEATURE_CALL |
1833                         EMPATHY_CONTACT_FEATURE_LOG |
1834                         EMPATHY_CONTACT_FEATURE_INFO));
1835                 gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
1836                                    priv->contact_list_view);
1837                 gtk_widget_show (priv->contact_list_view);
1838                 gtk_widget_show (priv->scrolled_window_contacts);
1839                 g_object_unref (store);
1840         } else if (!show) {
1841                 priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
1842                 gtk_widget_hide (priv->scrolled_window_contacts);
1843                 if (priv->contact_list_view != NULL) {
1844                         gtk_widget_destroy (priv->contact_list_view);
1845                         priv->contact_list_view = NULL;
1846                 }
1847         }
1848 }
1849
1850 void
1851 empathy_chat_set_show_contacts (EmpathyChat *chat,
1852                                 gboolean     show)
1853 {
1854         EmpathyChatPriv *priv = GET_PRIV (chat);
1855
1856         priv->show_contacts = show;
1857
1858         chat_update_contacts_visibility (chat);
1859
1860         g_object_notify (G_OBJECT (chat), "show-contacts");
1861 }
1862
1863 static void
1864 chat_remote_contact_changed_cb (EmpathyChat *chat)
1865 {
1866         EmpathyChatPriv *priv = GET_PRIV (chat);
1867
1868         if (priv->remote_contact != NULL) {
1869                 g_object_unref (priv->remote_contact);
1870                 priv->remote_contact = NULL;
1871         }
1872
1873         g_free (priv->id);
1874
1875         priv->id = g_strdup (empathy_tp_chat_get_id (priv->tp_chat));
1876         priv->remote_contact = empathy_tp_chat_get_remote_contact (priv->tp_chat);
1877         if (priv->remote_contact != NULL) {
1878                 g_object_ref (priv->remote_contact);
1879                 priv->handle_type = TP_HANDLE_TYPE_CONTACT;
1880         }
1881         else if (priv->tp_chat != NULL) {
1882                 TpChannel *channel;
1883
1884                 channel = empathy_tp_chat_get_channel (priv->tp_chat);
1885                 g_object_get (channel, "handle-type", &priv->handle_type, NULL);
1886         }
1887
1888         chat_update_contacts_visibility (chat);
1889
1890         g_object_notify (G_OBJECT (chat), "remote-contact");
1891         g_object_notify (G_OBJECT (chat), "id");
1892 }
1893
1894 static void
1895 chat_destroy_cb (EmpathyTpChat *tp_chat,
1896                  EmpathyChat   *chat)
1897 {
1898         EmpathyChatPriv *priv;
1899
1900         priv = GET_PRIV (chat);
1901
1902         if (!priv->tp_chat) {
1903                 return;
1904         }
1905
1906         chat_composing_remove_timeout (chat);
1907         g_object_unref (priv->tp_chat);
1908         priv->tp_chat = NULL;
1909         g_object_notify (G_OBJECT (chat), "tp-chat");
1910
1911         empathy_chat_view_append_event (chat->view, _("Disconnected"));
1912         gtk_widget_set_sensitive (chat->input_text_view, FALSE);
1913         empathy_chat_set_show_contacts (chat, FALSE);
1914 }
1915
1916 static void
1917 show_pending_messages (EmpathyChat *chat) {
1918         EmpathyChatPriv *priv = GET_PRIV (chat);
1919         const GList *messages, *l;
1920
1921         if (chat->view == NULL || priv->tp_chat == NULL)
1922                 return;
1923
1924         messages = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1925
1926         for (l = messages; l != NULL ; l = g_list_next (l)) {
1927                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1928                 chat_message_received (chat, message);
1929         }
1930         empathy_tp_chat_acknowledge_messages (priv->tp_chat, messages);
1931 }
1932
1933 static void
1934 chat_create_ui (EmpathyChat *chat)
1935 {
1936         EmpathyChatPriv *priv = GET_PRIV (chat);
1937         GtkBuilder      *gui;
1938         GList           *list = NULL;
1939         gchar           *filename;
1940         GtkTextBuffer   *buffer;
1941
1942         filename = empathy_file_lookup ("empathy-chat.ui",
1943                                         "libempathy-gtk");
1944         gui = empathy_builder_get_file (filename,
1945                                         "chat_widget", &priv->widget,
1946                                         "hpaned", &priv->hpaned,
1947                                         "vbox_left", &priv->vbox_left,
1948                                         "scrolled_window_chat", &priv->scrolled_window_chat,
1949                                         "scrolled_window_input", &priv->scrolled_window_input,
1950                                         "hbox_topic", &priv->hbox_topic,
1951                                         "label_topic", &priv->label_topic,
1952                                         "scrolled_window_contacts", &priv->scrolled_window_contacts,
1953                                         "info_bar_vbox", &priv->info_bar_vbox,
1954                                         NULL);
1955         g_free (filename);
1956
1957         /* Add message view. */
1958         chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
1959         g_signal_connect (chat->view, "focus_in_event",
1960                           G_CALLBACK (chat_text_view_focus_in_event_cb),
1961                           chat);
1962         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_chat),
1963                            GTK_WIDGET (chat->view));
1964         gtk_widget_show (GTK_WIDGET (chat->view));
1965
1966         /* Add input GtkTextView */
1967         chat->input_text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
1968                                               "pixels-above-lines", 2,
1969                                               "pixels-below-lines", 2,
1970                                               "pixels-inside-wrap", 1,
1971                                               "right-margin", 2,
1972                                               "left-margin", 2,
1973                                               "wrap-mode", GTK_WRAP_WORD_CHAR,
1974                                               NULL);
1975         g_signal_connect (chat->input_text_view, "key-press-event",
1976                           G_CALLBACK (chat_input_key_press_event_cb),
1977                           chat);
1978         g_signal_connect (chat->input_text_view, "size-request",
1979                           G_CALLBACK (chat_input_size_request_cb),
1980                           chat);
1981         g_signal_connect (chat->input_text_view, "realize",
1982                           G_CALLBACK (chat_input_realize_cb),
1983                           chat);
1984         g_signal_connect (chat->input_text_view, "populate-popup",
1985                           G_CALLBACK (chat_input_populate_popup_cb),
1986                           chat);
1987         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1988         g_signal_connect (buffer, "changed",
1989                           G_CALLBACK (chat_input_text_buffer_changed_cb),
1990                           chat);
1991         gtk_text_buffer_create_tag (buffer, "misspelled",
1992                                     "underline", PANGO_UNDERLINE_ERROR,
1993                                     NULL);
1994         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
1995                            chat->input_text_view);
1996         gtk_widget_show (chat->input_text_view);
1997
1998         /* Initialy hide the topic, will be shown if not empty */
1999         gtk_widget_hide (priv->hbox_topic);
2000
2001         /* Set widget focus order */
2002         list = g_list_append (NULL, priv->scrolled_window_input);
2003         gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
2004         g_list_free (list);
2005
2006         list = g_list_append (NULL, priv->vbox_left);
2007         list = g_list_append (list, priv->scrolled_window_contacts);
2008         gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
2009         g_list_free (list);
2010
2011         list = g_list_append (NULL, priv->hpaned);
2012         list = g_list_append (list, priv->hbox_topic);
2013         gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
2014         g_list_free (list);
2015
2016         /* Add the main widget in the chat widget */
2017         gtk_container_add (GTK_CONTAINER (chat), priv->widget);
2018         g_object_unref (gui);
2019 }
2020
2021 static void
2022 chat_size_request (GtkWidget      *widget,
2023                    GtkRequisition *requisition)
2024 {
2025   GtkBin *bin = GTK_BIN (widget);
2026   GtkWidget *child;
2027
2028   requisition->width = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2029   requisition->height = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2030
2031   child = gtk_bin_get_child (bin);
2032
2033   if (child && GTK_WIDGET_VISIBLE (child))
2034     {
2035       GtkRequisition child_requisition;
2036
2037       gtk_widget_size_request (child, &child_requisition);
2038
2039       requisition->width += child_requisition.width;
2040       requisition->height += child_requisition.height;
2041     }
2042 }
2043
2044 static void
2045 chat_size_allocate (GtkWidget     *widget,
2046                     GtkAllocation *allocation)
2047 {
2048   GtkBin *bin = GTK_BIN (widget);
2049   GtkAllocation child_allocation;
2050   GtkWidget *child;
2051
2052   widget->allocation = *allocation;
2053
2054   child = gtk_bin_get_child (bin);
2055
2056   if (child && GTK_WIDGET_VISIBLE (child))
2057     {
2058       child_allocation.x = allocation->x + gtk_container_get_border_width (GTK_CONTAINER (widget));
2059       child_allocation.y = allocation->y + gtk_container_get_border_width (GTK_CONTAINER (widget));
2060       child_allocation.width = MAX (allocation->width - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2061       child_allocation.height = MAX (allocation->height - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2062
2063       gtk_widget_size_allocate (child, &child_allocation);
2064     }
2065 }
2066
2067 static void
2068 chat_finalize (GObject *object)
2069 {
2070         EmpathyChat     *chat;
2071         EmpathyChatPriv *priv;
2072
2073         chat = EMPATHY_CHAT (object);
2074         priv = GET_PRIV (chat);
2075
2076         DEBUG ("Finalized: %p", object);
2077
2078         g_list_foreach (priv->input_history, (GFunc) chat_input_history_entry_free, NULL);
2079         g_list_free (priv->input_history);
2080
2081         g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
2082         g_list_free (priv->compositors);
2083
2084         chat_composing_remove_timeout (chat);
2085
2086         g_object_unref (priv->account_manager);
2087         g_object_unref (priv->log_manager);
2088
2089         if (priv->tp_chat) {
2090                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2091                         chat_destroy_cb, chat);
2092                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2093                         chat_message_received_cb, chat);
2094                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2095                         chat_send_error_cb, chat);
2096                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2097                         chat_state_changed_cb, chat);
2098                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2099                         chat_property_changed_cb, chat);
2100                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2101                         chat_members_changed_cb, chat);
2102                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2103                         chat_remote_contact_changed_cb, chat);
2104                 empathy_tp_chat_close (priv->tp_chat);
2105                 g_object_unref (priv->tp_chat);
2106         }
2107         if (priv->account) {
2108                 g_object_unref (priv->account);
2109         }
2110         if (priv->remote_contact) {
2111                 g_object_unref (priv->remote_contact);
2112         }
2113
2114         if (priv->block_events_timeout_id) {
2115                 g_source_remove (priv->block_events_timeout_id);
2116         }
2117
2118         g_free (priv->id);
2119         g_free (priv->name);
2120         g_free (priv->subject);
2121         g_completion_free (priv->completion);
2122
2123         G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
2124 }
2125
2126 static void
2127 chat_constructed (GObject *object)
2128 {
2129         EmpathyChat *chat = EMPATHY_CHAT (object);
2130
2131         chat_add_logs (chat);
2132         show_pending_messages (chat);
2133 }
2134
2135 static void
2136 empathy_chat_class_init (EmpathyChatClass *klass)
2137 {
2138         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2139         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
2140
2141         object_class->finalize = chat_finalize;
2142         object_class->get_property = chat_get_property;
2143         object_class->set_property = chat_set_property;
2144         object_class->constructed = chat_constructed;
2145
2146         widget_class->size_request = chat_size_request;
2147         widget_class->size_allocate = chat_size_allocate;
2148
2149         g_object_class_install_property (object_class,
2150                                          PROP_TP_CHAT,
2151                                          g_param_spec_object ("tp-chat",
2152                                                               "Empathy tp chat",
2153                                                               "The tp chat object",
2154                                                               EMPATHY_TYPE_TP_CHAT,
2155                                                               G_PARAM_CONSTRUCT |
2156                                                               G_PARAM_READWRITE |
2157                                                               G_PARAM_STATIC_STRINGS));
2158         g_object_class_install_property (object_class,
2159                                          PROP_ACCOUNT,
2160                                          g_param_spec_object ("account",
2161                                                               "Account of the chat",
2162                                                               "The account of the chat",
2163                                                               TP_TYPE_ACCOUNT,
2164                                                               G_PARAM_READABLE |
2165                                                               G_PARAM_STATIC_STRINGS));
2166         g_object_class_install_property (object_class,
2167                                          PROP_ID,
2168                                          g_param_spec_string ("id",
2169                                                               "Chat's id",
2170                                                               "The id of the chat",
2171                                                               NULL,
2172                                                               G_PARAM_READABLE |
2173                                                               G_PARAM_STATIC_STRINGS));
2174         g_object_class_install_property (object_class,
2175                                          PROP_NAME,
2176                                          g_param_spec_string ("name",
2177                                                               "Chat's name",
2178                                                               "The name of the chat",
2179                                                               NULL,
2180                                                               G_PARAM_READABLE |
2181                                                               G_PARAM_STATIC_STRINGS));
2182         g_object_class_install_property (object_class,
2183                                          PROP_SUBJECT,
2184                                          g_param_spec_string ("subject",
2185                                                               "Chat's subject",
2186                                                               "The subject or topic of the chat",
2187                                                               NULL,
2188                                                               G_PARAM_READABLE |
2189                                                               G_PARAM_STATIC_STRINGS));
2190         g_object_class_install_property (object_class,
2191                                          PROP_REMOTE_CONTACT,
2192                                          g_param_spec_object ("remote-contact",
2193                                                               "The remote contact",
2194                                                               "The remote contact is any",
2195                                                               EMPATHY_TYPE_CONTACT,
2196                                                               G_PARAM_READABLE |
2197                                                               G_PARAM_STATIC_STRINGS));
2198         g_object_class_install_property (object_class,
2199                                          PROP_SHOW_CONTACTS,
2200                                          g_param_spec_boolean ("show-contacts",
2201                                                                "Contacts' visibility",
2202                                                                "The visibility of the contacts' list",
2203                                                                TRUE,
2204                                                                G_PARAM_READWRITE |
2205                                                                G_PARAM_STATIC_STRINGS));
2206
2207         signals[COMPOSING] =
2208                 g_signal_new ("composing",
2209                               G_OBJECT_CLASS_TYPE (object_class),
2210                               G_SIGNAL_RUN_LAST,
2211                               0,
2212                               NULL, NULL,
2213                               g_cclosure_marshal_VOID__BOOLEAN,
2214                               G_TYPE_NONE,
2215                               1, G_TYPE_BOOLEAN);
2216
2217         signals[NEW_MESSAGE] =
2218                 g_signal_new ("new-message",
2219                               G_OBJECT_CLASS_TYPE (object_class),
2220                               G_SIGNAL_RUN_LAST,
2221                               0,
2222                               NULL, NULL,
2223                               g_cclosure_marshal_VOID__OBJECT,
2224                               G_TYPE_NONE,
2225                               1, EMPATHY_TYPE_MESSAGE);
2226
2227         g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
2228 }
2229
2230 static gboolean
2231 chat_block_events_timeout_cb (gpointer data)
2232 {
2233         EmpathyChatPriv *priv = GET_PRIV (data);
2234
2235         priv->block_events_timeout_id = 0;
2236
2237         return FALSE;
2238 }
2239
2240 static void
2241 account_manager_prepared_cb (GObject *source_object,
2242                              GAsyncResult *result,
2243                              gpointer user_data)
2244 {
2245         GList *accounts, *l;
2246         TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
2247         EmpathyChat *chat = user_data;
2248         GError *error = NULL;
2249
2250         if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
2251                 DEBUG ("Failed to prepare the account manager: %s", error->message);
2252                 g_error_free (error);
2253                 return;
2254         }
2255
2256         accounts = tp_account_manager_get_valid_accounts (account_manager);
2257
2258         for (l = accounts; l != NULL; l = l->next) {
2259                 TpAccount *account = l->data;
2260                 empathy_signal_connect_weak (account, "status-changed",
2261                                              G_CALLBACK (chat_new_connection_cb),
2262                                              G_OBJECT (chat));
2263         }
2264
2265         g_list_free (accounts);
2266 }
2267
2268 static void
2269 empathy_chat_init (EmpathyChat *chat)
2270 {
2271         EmpathyChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
2272                 EMPATHY_TYPE_CHAT, EmpathyChatPriv);
2273
2274         chat->priv = priv;
2275         priv->log_manager = empathy_log_manager_dup_singleton ();
2276         priv->contacts_width = -1;
2277         priv->input_history = NULL;
2278         priv->input_history_current = NULL;
2279         priv->account_manager = tp_account_manager_dup ();
2280
2281         tp_account_manager_prepare_async (priv->account_manager, NULL,
2282                                           account_manager_prepared_cb, chat);
2283
2284         empathy_conf_get_bool (empathy_conf_get (),
2285                                EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS,
2286                                &priv->show_contacts);
2287
2288         /* Block events for some time to avoid having "has come online" or
2289          * "joined" messages. */
2290         priv->block_events_timeout_id =
2291                 g_timeout_add_seconds (1, chat_block_events_timeout_cb, chat);
2292
2293         /* Add nick name completion */
2294         priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_name);
2295         g_completion_set_compare (priv->completion, chat_contacts_completion_func);
2296
2297         chat_create_ui (chat);
2298 }
2299
2300 EmpathyChat *
2301 empathy_chat_new (EmpathyTpChat *tp_chat)
2302 {
2303         return g_object_new (EMPATHY_TYPE_CHAT, "tp-chat", tp_chat, NULL);
2304 }
2305
2306 EmpathyTpChat *
2307 empathy_chat_get_tp_chat (EmpathyChat *chat)
2308 {
2309         EmpathyChatPriv *priv = GET_PRIV (chat);
2310
2311         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2312
2313         return priv->tp_chat;
2314 }
2315
2316 static void display_password_info_bar (EmpathyChat *self,
2317                           gboolean retry);
2318
2319 static void
2320 provide_password_cb (GObject *tp_chat,
2321                           GAsyncResult *res,
2322                           gpointer user_data)
2323 {
2324         EmpathyChat *self = EMPATHY_CHAT (user_data);
2325         EmpathyChatPriv *priv = GET_PRIV (self);
2326         GError *error = NULL;
2327
2328         if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat), res,
2329                                &error)) {
2330                 DEBUG ("error: %s", error->message);
2331                 /* FIXME: what should we do if that's another error? Close the channel?
2332                  * Display the raw D-Bus error to the user isn't very useful */
2333                 if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED))
2334                         display_password_info_bar (self, TRUE);
2335                 g_error_free (error);
2336                 return;
2337         }
2338
2339         /* Room joined */
2340         gtk_widget_set_sensitive (priv->hpaned, TRUE);
2341         gtk_widget_grab_focus (self->input_text_view);
2342 }
2343
2344 static void
2345 password_infobar_response_cb (GtkWidget *info_bar,
2346                           gint response_id,
2347                           EmpathyChat *self)
2348 {
2349         EmpathyChatPriv *priv = GET_PRIV (self);
2350         GtkWidget *entry;
2351         const gchar *password;
2352
2353         if (response_id != GTK_RESPONSE_OK)
2354                 goto out;
2355
2356         entry = g_object_get_data (G_OBJECT (info_bar), "password-entry");
2357         g_assert (entry != NULL);
2358
2359         password = gtk_entry_get_text (GTK_ENTRY (entry));
2360
2361         empathy_tp_chat_provide_password_async (priv->tp_chat, password,
2362                                provide_password_cb, self);
2363
2364 out:
2365         gtk_widget_destroy (info_bar);
2366 }
2367
2368 static void
2369 password_entry_activate_cb (GtkWidget *entry,
2370                           GtkWidget *info_bar)
2371 {
2372         gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2373 }
2374
2375 static void
2376 passwd_join_button_cb (GtkButton *button,
2377                           GtkWidget *info_bar)
2378 {
2379         gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2380 }
2381
2382 static void
2383 display_password_info_bar (EmpathyChat *self,
2384                           gboolean retry)
2385 {
2386         EmpathyChatPriv *priv = GET_PRIV (self);
2387         GtkWidget *info_bar;
2388         GtkWidget *content_area;
2389         GtkWidget *hbox;
2390         GtkWidget *image;
2391         GtkWidget *label;
2392         GtkWidget *entry;
2393         GtkWidget *alig;
2394         GtkWidget *button;
2395         GtkMessageType type;
2396         const gchar *msg, *button_label;
2397
2398         if (retry) {
2399                 /* Previous password was wrong */
2400                 type = GTK_MESSAGE_ERROR;
2401                 msg = _("Wrong password; please try again:");
2402                 button_label = _("Retry");
2403         }
2404         else {
2405                 /* First time we're trying to join */
2406                 type = GTK_MESSAGE_QUESTION;
2407                 msg = _("This room is protected by a password:");
2408                 button_label = _("Join");
2409         }
2410
2411         info_bar = gtk_info_bar_new ();
2412         gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), type);
2413
2414         content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2415
2416         hbox = gtk_hbox_new (FALSE, 3);
2417         gtk_container_add (GTK_CONTAINER (content_area), hbox);
2418
2419         /* Add image */
2420         image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
2421                                GTK_ICON_SIZE_DIALOG);
2422         gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2423
2424         /* Add message */
2425         label = gtk_label_new (msg);
2426         gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
2427
2428         /* Add password entry */
2429         entry = gtk_entry_new ();
2430         gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
2431         gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
2432
2433         g_signal_connect (entry, "activate",
2434                                G_CALLBACK (password_entry_activate_cb), info_bar);
2435
2436         /* Focus the password entry once it's realized */
2437         g_signal_connect (entry, "realize", G_CALLBACK (gtk_widget_grab_focus), NULL);
2438
2439         /* Add 'Join' button */
2440         alig = gtk_alignment_new (0, 0.5, 0, 0);
2441
2442         button = gtk_button_new_with_label (button_label);
2443         gtk_container_add (GTK_CONTAINER (alig), button);
2444         gtk_box_pack_start (GTK_BOX (hbox), alig, FALSE, FALSE, 0);
2445
2446         g_signal_connect (button, "clicked", G_CALLBACK (passwd_join_button_cb),
2447                                info_bar);
2448
2449         g_object_set_data (G_OBJECT (info_bar), "password-entry", entry);
2450
2451         gtk_box_pack_start (GTK_BOX (priv->info_bar_vbox), info_bar,
2452                                FALSE, FALSE, 3);
2453         gtk_widget_show_all (hbox);
2454
2455         g_signal_connect (info_bar, "response",
2456                                G_CALLBACK (password_infobar_response_cb), self);
2457
2458         gtk_widget_show_all (info_bar);
2459 }
2460
2461 static void
2462 chat_password_needed_changed_cb (EmpathyChat *self)
2463 {
2464         EmpathyChatPriv *priv = GET_PRIV (self);
2465
2466         if (empathy_tp_chat_password_needed (priv->tp_chat)) {
2467                 display_password_info_bar (self, FALSE);
2468                 gtk_widget_set_sensitive (priv->hpaned, FALSE);
2469         }
2470 }
2471
2472 void
2473 empathy_chat_set_tp_chat (EmpathyChat   *chat,
2474                           EmpathyTpChat *tp_chat)
2475 {
2476         EmpathyChatPriv *priv = GET_PRIV (chat);
2477         TpConnection    *connection;
2478         GPtrArray       *properties;
2479
2480         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2481         g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
2482         g_return_if_fail (empathy_tp_chat_is_ready (tp_chat));
2483
2484         if (priv->tp_chat) {
2485                 return;
2486         }
2487
2488         if (priv->account) {
2489                 g_object_unref (priv->account);
2490         }
2491
2492         priv->tp_chat = g_object_ref (tp_chat);
2493         connection = empathy_tp_chat_get_connection (priv->tp_chat);
2494         priv->account = g_object_ref (empathy_get_account_for_connection (connection));
2495
2496         g_signal_connect (tp_chat, "destroy",
2497                           G_CALLBACK (chat_destroy_cb),
2498                           chat);
2499         g_signal_connect (tp_chat, "message-received",
2500                           G_CALLBACK (chat_message_received_cb),
2501                           chat);
2502         g_signal_connect (tp_chat, "send-error",
2503                           G_CALLBACK (chat_send_error_cb),
2504                           chat);
2505         g_signal_connect (tp_chat, "chat-state-changed",
2506                           G_CALLBACK (chat_state_changed_cb),
2507                           chat);
2508         g_signal_connect (tp_chat, "property-changed",
2509                           G_CALLBACK (chat_property_changed_cb),
2510                           chat);
2511         g_signal_connect (tp_chat, "members-changed",
2512                           G_CALLBACK (chat_members_changed_cb),
2513                           chat);
2514         g_signal_connect_swapped (tp_chat, "notify::remote-contact",
2515                                   G_CALLBACK (chat_remote_contact_changed_cb),
2516                                   chat);
2517         g_signal_connect_swapped (tp_chat, "notify::password-needed",
2518                                   G_CALLBACK (chat_password_needed_changed_cb),
2519                                   chat);
2520
2521         /* Get initial value of properties */
2522         properties = empathy_tp_chat_get_properties (priv->tp_chat);
2523         if (properties != NULL) {
2524                 guint i;
2525
2526                 for (i = 0; i < properties->len; i++) {
2527                         EmpathyTpChatProperty *property;
2528
2529                         property = g_ptr_array_index (properties, i);
2530                         if (property->value == NULL)
2531                                 continue;
2532
2533                         chat_property_changed_cb (priv->tp_chat,
2534                                                   property->name,
2535                                                   property->value,
2536                                                   chat);
2537                 }
2538         }
2539
2540         chat_remote_contact_changed_cb (chat);
2541
2542         if (chat->input_text_view) {
2543                 gtk_widget_set_sensitive (chat->input_text_view, TRUE);
2544                 if (priv->block_events_timeout_id == 0) {
2545                         empathy_chat_view_append_event (chat->view, _("Connected"));
2546                 }
2547         }
2548
2549         g_object_notify (G_OBJECT (chat), "tp-chat");
2550         g_object_notify (G_OBJECT (chat), "id");
2551         g_object_notify (G_OBJECT (chat), "account");
2552
2553         /* This is a noop when tp-chat is set at object construction time and causes
2554          * the pending messages to be show when it's set on the object after it has
2555          * been created */
2556         show_pending_messages (chat);
2557
2558         /* check if a password is needed */
2559         chat_password_needed_changed_cb (chat);
2560 }
2561
2562 TpAccount *
2563 empathy_chat_get_account (EmpathyChat *chat)
2564 {
2565         EmpathyChatPriv *priv = GET_PRIV (chat);
2566
2567         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2568
2569         return priv->account;
2570 }
2571
2572 const gchar *
2573 empathy_chat_get_id (EmpathyChat *chat)
2574 {
2575         EmpathyChatPriv *priv = GET_PRIV (chat);
2576
2577         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2578
2579         return priv->id;
2580 }
2581
2582 const gchar *
2583 empathy_chat_get_name (EmpathyChat *chat)
2584 {
2585         EmpathyChatPriv *priv = GET_PRIV (chat);
2586         const gchar *ret;
2587
2588         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2589
2590         ret = priv->name;
2591         if (!ret && priv->remote_contact) {
2592                 ret = empathy_contact_get_name (priv->remote_contact);
2593         }
2594
2595         if (!ret)
2596                 ret = priv->id;
2597
2598         return ret ? ret : _("Conversation");
2599 }
2600
2601 const gchar *
2602 empathy_chat_get_subject (EmpathyChat *chat)
2603 {
2604         EmpathyChatPriv *priv = GET_PRIV (chat);
2605
2606         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2607
2608         return priv->subject;
2609 }
2610
2611 EmpathyContact *
2612 empathy_chat_get_remote_contact (EmpathyChat *chat)
2613 {
2614         EmpathyChatPriv *priv = GET_PRIV (chat);
2615
2616         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2617
2618         return priv->remote_contact;
2619 }
2620
2621 GtkWidget *
2622 empathy_chat_get_contact_menu (EmpathyChat *chat)
2623 {
2624         EmpathyChatPriv *priv = GET_PRIV (chat);
2625         GtkWidget       *menu = NULL;
2626
2627         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2628
2629         if (priv->remote_contact) {
2630                 menu = empathy_contact_menu_new (priv->remote_contact,
2631                                                  EMPATHY_CONTACT_FEATURE_CALL |
2632                                                  EMPATHY_CONTACT_FEATURE_LOG |
2633                                                  EMPATHY_CONTACT_FEATURE_INFO);
2634         }
2635         else if (priv->contact_list_view) {
2636                 EmpathyContactListView *view;
2637
2638                 view = EMPATHY_CONTACT_LIST_VIEW (priv->contact_list_view);
2639                 menu = empathy_contact_list_view_get_contact_menu (view);
2640         }
2641
2642         return menu;
2643 }
2644
2645 void
2646 empathy_chat_clear (EmpathyChat *chat)
2647 {
2648         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2649
2650         empathy_chat_view_clear (chat->view);
2651 }
2652
2653 void
2654 empathy_chat_scroll_down (EmpathyChat *chat)
2655 {
2656         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2657
2658         empathy_chat_view_scroll_down (chat->view);
2659 }
2660
2661 void
2662 empathy_chat_cut (EmpathyChat *chat)
2663 {
2664         GtkTextBuffer *buffer;
2665
2666         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2667
2668         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2669         if (gtk_text_buffer_get_has_selection (buffer)) {
2670                 GtkClipboard *clipboard;
2671
2672                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2673
2674                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
2675         }
2676 }
2677
2678 void
2679 empathy_chat_copy (EmpathyChat *chat)
2680 {
2681         GtkTextBuffer *buffer;
2682
2683         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2684
2685         if (empathy_chat_view_get_has_selection (chat->view)) {
2686                 empathy_chat_view_copy_clipboard (chat->view);
2687                 return;
2688         }
2689
2690         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2691         if (gtk_text_buffer_get_has_selection (buffer)) {
2692                 GtkClipboard *clipboard;
2693
2694                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2695
2696                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2697         }
2698 }
2699
2700 void
2701 empathy_chat_paste (EmpathyChat *chat)
2702 {
2703         GtkTextBuffer *buffer;
2704         GtkClipboard  *clipboard;
2705         EmpathyChatPriv *priv;
2706
2707         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2708
2709         priv = GET_PRIV (chat);
2710
2711         if (priv->tp_chat == NULL ||
2712             !GTK_WIDGET_IS_SENSITIVE (chat->input_text_view))
2713                 return;
2714
2715         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2716         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2717
2718         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
2719 }
2720
2721 void
2722 empathy_chat_correct_word (EmpathyChat  *chat,
2723                           GtkTextIter *start,
2724                           GtkTextIter *end,
2725                           const gchar *new_word)
2726 {
2727         GtkTextBuffer *buffer;
2728
2729         g_return_if_fail (chat != NULL);
2730         g_return_if_fail (new_word != NULL);
2731
2732         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2733
2734         gtk_text_buffer_delete (buffer, start, end);
2735         gtk_text_buffer_insert (buffer, start,
2736                                 new_word,
2737                                 -1);
2738 }
2739
2740 gboolean
2741 empathy_chat_is_room (EmpathyChat *chat)
2742 {
2743         EmpathyChatPriv *priv = GET_PRIV (chat);
2744
2745         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
2746
2747         return (priv->handle_type == TP_HANDLE_TYPE_ROOM);
2748 }
2749