]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat.c
Updated Basque language
[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/util.h>
38
39 #include <libempathy/empathy-account-manager.h>
40 #include <libempathy/empathy-log-manager.h>
41 #include <libempathy/empathy-contact-list.h>
42 #include <libempathy/empathy-utils.h>
43 #include <libempathy/empathy-dispatcher.h>
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         EmpathyAccount    *account;
68         gchar             *id;
69         gchar             *name;
70         gchar             *subject;
71         EmpathyContact    *remote_contact;
72         gboolean           show_contacts;
73
74         EmpathyLogManager *log_manager;
75         EmpathyAccountManager *account_manager;
76         GSList            *sent_messages;
77         gint               sent_messages_index;
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 } EmpathyChatPriv;
96
97 enum {
98         COMPOSING,
99         NEW_MESSAGE,
100         LAST_SIGNAL
101 };
102
103 enum {
104         PROP_0,
105         PROP_TP_CHAT,
106         PROP_ACCOUNT,
107         PROP_ID,
108         PROP_NAME,
109         PROP_SUBJECT,
110         PROP_REMOTE_CONTACT,
111         PROP_SHOW_CONTACTS,
112 };
113
114 static guint signals[LAST_SIGNAL] = { 0 };
115
116 G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BIN);
117
118 static void
119 chat_get_property (GObject    *object,
120                    guint       param_id,
121                    GValue     *value,
122                    GParamSpec *pspec)
123 {
124         EmpathyChat *chat = EMPATHY_CHAT (object);
125         EmpathyChatPriv *priv = GET_PRIV (object);
126
127         switch (param_id) {
128         case PROP_TP_CHAT:
129                 g_value_set_object (value, priv->tp_chat);
130                 break;
131         case PROP_ACCOUNT:
132                 g_value_set_object (value, priv->account);
133                 break;
134         case PROP_NAME:
135                 g_value_set_string (value, empathy_chat_get_name (chat));
136                 break;
137         case PROP_ID:
138                 g_value_set_string (value, priv->id);
139                 break;
140         case PROP_SUBJECT:
141                 g_value_set_string (value, priv->subject);
142                 break;
143         case PROP_REMOTE_CONTACT:
144                 g_value_set_object (value, priv->remote_contact);
145                 break;
146         case PROP_SHOW_CONTACTS:
147                 g_value_set_boolean (value, priv->show_contacts);
148                 break;
149         default:
150                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
151                 break;
152         };
153 }
154
155 static void
156 chat_set_property (GObject      *object,
157                    guint         param_id,
158                    const GValue *value,
159                    GParamSpec   *pspec)
160 {
161         EmpathyChat *chat = EMPATHY_CHAT (object);
162
163         switch (param_id) {
164         case PROP_TP_CHAT:
165                 empathy_chat_set_tp_chat (chat, EMPATHY_TP_CHAT (g_value_get_object (value)));
166                 break;
167         case PROP_SHOW_CONTACTS:
168                 empathy_chat_set_show_contacts (chat, g_value_get_boolean (value));
169                 break;
170         default:
171                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
172                 break;
173         };
174 }
175
176 static void
177 chat_connect_channel_reconnected (EmpathyDispatchOperation *dispatch,
178                                   const GError             *error,
179                                   gpointer                  user_data)
180 {
181         EmpathyChat *chat = EMPATHY_CHAT (user_data);
182         EmpathyTpChat *tpchat;
183
184         if (error != NULL) {
185                 empathy_chat_view_append_event (chat->view,
186                         _("Failed to reconnect this chat"));
187                 return;
188         }
189
190         tpchat = EMPATHY_TP_CHAT (
191                 empathy_dispatch_operation_get_channel_wrapper (dispatch));
192
193         if (empathy_dispatch_operation_claim (dispatch)) {
194                 empathy_chat_set_tp_chat (chat, tpchat);
195         }
196 }
197
198 static void
199 chat_new_connection_cb (EmpathyAccountManager *manager,
200                         TpConnection *connection,
201                         EmpathyChat *chat)
202 {
203         EmpathyChatPriv *priv = GET_PRIV (chat);
204         EmpathyAccount *account;
205
206         account = empathy_account_manager_get_account_for_connection (manager,
207                 connection);
208         if (!priv->tp_chat && account == priv->account &&
209             priv->handle_type != TP_HANDLE_TYPE_NONE &&
210             !EMP_STR_EMPTY (priv->id)) {
211
212                 DEBUG ("Account reconnected, request a new Text channel");
213
214                 switch (priv->handle_type) {
215                         case TP_HANDLE_TYPE_CONTACT:
216                                 empathy_dispatcher_chat_with_contact_id (
217                                         connection, priv->id,
218                                         chat_connect_channel_reconnected,
219                                         chat);
220                                 break;
221                         case TP_HANDLE_TYPE_ROOM:
222                                 empathy_dispatcher_join_muc (connection,
223                                         priv->id,
224                                         chat_connect_channel_reconnected,
225                                         chat);
226                                 break;
227                         default:
228                                 g_assert_not_reached ();
229                                 break;
230                 }
231         }
232 }
233
234 static void
235 chat_composing_remove_timeout (EmpathyChat *chat)
236 {
237         EmpathyChatPriv *priv;
238
239         priv = GET_PRIV (chat);
240
241         if (priv->composing_stop_timeout_id) {
242                 g_source_remove (priv->composing_stop_timeout_id);
243                 priv->composing_stop_timeout_id = 0;
244         }
245 }
246
247 static gboolean
248 chat_composing_stop_timeout_cb (EmpathyChat *chat)
249 {
250         EmpathyChatPriv *priv;
251
252         priv = GET_PRIV (chat);
253
254         priv->composing_stop_timeout_id = 0;
255         empathy_tp_chat_set_state (priv->tp_chat,
256                                    TP_CHANNEL_CHAT_STATE_PAUSED);
257
258         return FALSE;
259 }
260
261 static void
262 chat_composing_start (EmpathyChat *chat)
263 {
264         EmpathyChatPriv *priv;
265
266         priv = GET_PRIV (chat);
267
268         if (priv->composing_stop_timeout_id) {
269                 /* Just restart the timeout */
270                 chat_composing_remove_timeout (chat);
271         } else {
272                 empathy_tp_chat_set_state (priv->tp_chat,
273                                            TP_CHANNEL_CHAT_STATE_COMPOSING);
274         }
275
276         priv->composing_stop_timeout_id = g_timeout_add_seconds (
277                 COMPOSING_STOP_TIMEOUT,
278                 (GSourceFunc) chat_composing_stop_timeout_cb,
279                 chat);
280 }
281
282 static void
283 chat_composing_stop (EmpathyChat *chat)
284 {
285         EmpathyChatPriv *priv;
286
287         priv = GET_PRIV (chat);
288
289         chat_composing_remove_timeout (chat);
290         empathy_tp_chat_set_state (priv->tp_chat,
291                                    TP_CHANNEL_CHAT_STATE_ACTIVE);
292 }
293
294 static void
295 chat_sent_message_add (EmpathyChat  *chat,
296                        const gchar *str)
297 {
298         EmpathyChatPriv *priv;
299         GSList         *list;
300         GSList         *item;
301
302         priv = GET_PRIV (chat);
303
304         /* Save the sent message in our repeat buffer */
305         list = priv->sent_messages;
306
307         /* Remove any other occurances of this msg */
308         while ((item = g_slist_find_custom (list, str, (GCompareFunc) strcmp)) != NULL) {
309                 list = g_slist_remove_link (list, item);
310                 g_free (item->data);
311                 g_slist_free1 (item);
312         }
313
314         /* Trim the list to the last 10 items */
315         while (g_slist_length (list) > 10) {
316                 item = g_slist_last (list);
317                 if (item) {
318                         list = g_slist_remove_link (list, item);
319                         g_free (item->data);
320                         g_slist_free1 (item);
321                 }
322         }
323
324         /* Add new message */
325         list = g_slist_prepend (list, g_strdup (str));
326
327         /* Set list and reset the index */
328         priv->sent_messages = list;
329         priv->sent_messages_index = -1;
330 }
331
332 static const gchar *
333 chat_sent_message_get_next (EmpathyChat *chat)
334 {
335         EmpathyChatPriv *priv;
336         gint            max;
337
338         priv = GET_PRIV (chat);
339
340         if (!priv->sent_messages) {
341                 DEBUG ("No sent messages, next message is NULL");
342                 return NULL;
343         }
344
345         max = g_slist_length (priv->sent_messages) - 1;
346
347         if (priv->sent_messages_index < max) {
348                 priv->sent_messages_index++;
349         }
350
351         DEBUG ("Returning next message index:%d", priv->sent_messages_index);
352
353         return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
354 }
355
356 static const gchar *
357 chat_sent_message_get_last (EmpathyChat *chat)
358 {
359         EmpathyChatPriv *priv;
360
361         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
362
363         priv = GET_PRIV (chat);
364
365         if (!priv->sent_messages) {
366                 DEBUG ("No sent messages, last message is NULL");
367                 return NULL;
368         }
369
370         if (priv->sent_messages_index >= 0) {
371                 priv->sent_messages_index--;
372         }
373
374         DEBUG ("Returning last message index:%d", priv->sent_messages_index);
375
376         return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
377 }
378
379 static void
380 chat_send (EmpathyChat  *chat,
381            const gchar *msg)
382 {
383         EmpathyChatPriv *priv;
384         EmpathyMessage  *message;
385
386         if (EMP_STR_EMPTY (msg)) {
387                 return;
388         }
389
390         priv = GET_PRIV (chat);
391
392         chat_sent_message_add (chat, msg);
393
394         if (strcmp (msg, "/clear") == 0) {
395                 empathy_chat_view_clear (chat->view);
396                 return;
397         }
398
399         message = empathy_message_new_from_entry (msg);
400
401         if (message == NULL) {
402                 empathy_chat_view_append_event (chat->view,
403                         _("Unsupported command"));
404         } else {
405                 empathy_tp_chat_send (priv->tp_chat, message);
406                 g_object_unref (message);
407         }
408 }
409
410 static void
411 chat_input_text_view_send (EmpathyChat *chat)
412 {
413         EmpathyChatPriv *priv;
414         GtkTextBuffer  *buffer;
415         GtkTextIter     start, end;
416         gchar          *msg;
417
418         priv = GET_PRIV (chat);
419
420         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
421
422         gtk_text_buffer_get_bounds (buffer, &start, &end);
423         msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
424
425         /* clear the input field */
426         gtk_text_buffer_set_text (buffer, "", -1);
427
428         chat_send (chat, msg);
429         g_free (msg);
430 }
431
432 static void
433 chat_state_changed_cb (EmpathyTpChat      *tp_chat,
434                        EmpathyContact     *contact,
435                        TpChannelChatState  state,
436                        EmpathyChat        *chat)
437 {
438         EmpathyChatPriv *priv;
439         GList          *l;
440         gboolean        was_composing;
441
442         priv = GET_PRIV (chat);
443
444         if (empathy_contact_is_user (contact)) {
445                 /* We don't care about our own chat state */
446                 return;
447         }
448
449         was_composing = (priv->compositors != NULL);
450
451         /* Find the contact in the list. After that l is the list elem or NULL */
452         for (l = priv->compositors; l; l = l->next) {
453                 if (contact == l->data) {
454                         break;
455                 }
456         }
457
458         switch (state) {
459         case TP_CHANNEL_CHAT_STATE_GONE:
460         case TP_CHANNEL_CHAT_STATE_INACTIVE:
461         case TP_CHANNEL_CHAT_STATE_PAUSED:
462         case TP_CHANNEL_CHAT_STATE_ACTIVE:
463                 /* Contact is not composing */
464                 if (l) {
465                         priv->compositors = g_list_remove_link (priv->compositors, l);
466                         g_object_unref (l->data);
467                         g_list_free1 (l);
468                 }
469                 break;
470         case TP_CHANNEL_CHAT_STATE_COMPOSING:
471                 /* Contact is composing */
472                 if (!l) {
473                         priv->compositors = g_list_prepend (priv->compositors,
474                                                             g_object_ref (contact));
475                 }
476                 break;
477         default:
478                 g_assert_not_reached ();
479         }
480
481         DEBUG ("Was composing: %s now composing: %s",
482                 was_composing ? "yes" : "no",
483                 priv->compositors ? "yes" : "no");
484
485         if ((was_composing && !priv->compositors) ||
486             (!was_composing && priv->compositors)) {
487                 /* Composing state changed */
488                 g_signal_emit (chat, signals[COMPOSING], 0,
489                                priv->compositors != NULL);
490         }
491 }
492
493 static void
494 chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
495 {
496         EmpathyChatPriv *priv = GET_PRIV (chat);
497         EmpathyContact  *sender;
498
499         sender = empathy_message_get_sender (message);
500
501         DEBUG ("Appending new message from %s (%d)",
502                 empathy_contact_get_name (sender),
503                 empathy_contact_get_handle (sender));
504
505         empathy_chat_view_append_message (chat->view, message);
506
507         /* We received a message so the contact is no longer composing */
508         chat_state_changed_cb (priv->tp_chat, sender,
509                                TP_CHANNEL_CHAT_STATE_ACTIVE,
510                                chat);
511
512         g_signal_emit (chat, signals[NEW_MESSAGE], 0, message);
513 }
514
515 static void
516 chat_message_received_cb (EmpathyTpChat  *tp_chat,
517                           EmpathyMessage *message,
518                           EmpathyChat    *chat)
519 {
520         chat_message_received (chat, message);
521         empathy_tp_chat_acknowledge_message (tp_chat, message);
522 }
523
524 static void
525 chat_send_error_cb (EmpathyTpChat          *tp_chat,
526                     const gchar            *message_body,
527                     TpChannelTextSendError  error_code,
528                     EmpathyChat            *chat)
529 {
530         const gchar *error;
531         gchar       *str;
532
533         switch (error_code) {
534         case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
535                 error = _("offline");
536                 break;
537         case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
538                 error = _("invalid contact");
539                 break;
540         case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
541                 error = _("permission denied");
542                 break;
543         case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
544                 error = _("too long message");
545                 break;
546         case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
547                 error = _("not implemented");
548                 break;
549         default:
550                 error = _("unknown");
551                 break;
552         }
553
554         str = g_strdup_printf (_("Error sending message '%s': %s"),
555                                message_body,
556                                error);
557         empathy_chat_view_append_event (chat->view, str);
558         g_free (str);
559 }
560
561 static void
562 chat_property_changed_cb (EmpathyTpChat *tp_chat,
563                           const gchar   *name,
564                           GValue        *value,
565                           EmpathyChat   *chat)
566 {
567         EmpathyChatPriv *priv = GET_PRIV (chat);
568
569         if (!tp_strdiff (name, "subject")) {
570                 g_free (priv->subject);
571                 priv->subject = g_value_dup_string (value);
572                 g_object_notify (G_OBJECT (chat), "subject");
573
574                 if (EMP_STR_EMPTY (priv->subject)) {
575                         gtk_widget_hide (priv->hbox_topic);
576                 } else {
577                         gtk_label_set_text (GTK_LABEL (priv->label_topic), priv->subject);
578                         gtk_widget_show (priv->hbox_topic);
579                 }
580                 if (priv->block_events_timeout_id == 0) {
581                         gchar *str;
582
583                         if (!EMP_STR_EMPTY (priv->subject)) {
584                                 str = g_strdup_printf (_("Topic set to: %s"), priv->subject);
585                         } else {
586                                 str = g_strdup (_("No topic defined"));
587                         }
588                         empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str);
589                         g_free (str);
590                 }
591         }
592         else if (!tp_strdiff (name, "name")) {
593                 g_free (priv->name);
594                 priv->name = g_value_dup_string (value);
595                 g_object_notify (G_OBJECT (chat), "name");
596         }
597 }
598
599 static void
600 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
601                                    EmpathyChat    *chat)
602 {
603         EmpathyChatPriv *priv;
604         GtkTextIter     start, end;
605         gchar          *str;
606         gboolean        spell_checker = FALSE;
607
608         priv = GET_PRIV (chat);
609
610         if (gtk_text_buffer_get_char_count (buffer) == 0) {
611                 chat_composing_stop (chat);
612         } else {
613                 chat_composing_start (chat);
614         }
615
616         empathy_conf_get_bool (empathy_conf_get (),
617                            EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
618                            &spell_checker);
619
620         gtk_text_buffer_get_start_iter (buffer, &start);
621
622         if (!spell_checker) {
623                 gtk_text_buffer_get_end_iter (buffer, &end);
624                 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
625                 return;
626         }
627
628         if (!empathy_spell_supported ()) {
629                 return;
630         }
631
632         /* NOTE: this is really inefficient, we shouldn't have to
633            reiterate the whole buffer each time and check each work
634            every time. */
635         while (TRUE) {
636                 gboolean correct = FALSE;
637
638                 /* if at start */
639                 if (gtk_text_iter_is_start (&start)) {
640                         end = start;
641
642                         if (!gtk_text_iter_forward_word_end (&end)) {
643                                 /* no whole word yet */
644                                 break;
645                         }
646                 } else {
647                         if (!gtk_text_iter_forward_word_end (&end)) {
648                                 /* must be the end of the buffer */
649                                 break;
650                         }
651
652                         start = end;
653                         gtk_text_iter_backward_word_start (&start);
654                 }
655
656                 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
657
658                 /* spell check string if not a command */
659                 if (str[0] != '/') {
660                         correct = empathy_spell_check (str);
661                 } else {
662                         correct = TRUE;
663                 }
664
665                 if (!correct) {
666                         gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
667                 } else {
668                         gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
669                 }
670
671                 g_free (str);
672
673                 /* set start iter to the end iters position */
674                 start = end;
675         }
676 }
677
678 static gboolean
679 chat_input_key_press_event_cb (GtkWidget   *widget,
680                                GdkEventKey *event,
681                                EmpathyChat *chat)
682 {
683         EmpathyChatPriv *priv;
684         GtkAdjustment  *adj;
685         gdouble         val;
686         GtkWidget      *text_view_sw;
687
688         priv = GET_PRIV (chat);
689
690         /* Catch ctrl+up/down so we can traverse messages we sent */
691         if ((event->state & GDK_CONTROL_MASK) &&
692             (event->keyval == GDK_Up ||
693              event->keyval == GDK_Down)) {
694                 GtkTextBuffer *buffer;
695                 const gchar   *str;
696
697                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
698
699                 if (event->keyval == GDK_Up) {
700                         str = chat_sent_message_get_next (chat);
701                 } else {
702                         str = chat_sent_message_get_last (chat);
703                 }
704
705                 g_signal_handlers_block_by_func (buffer,
706                                                  chat_input_text_buffer_changed_cb,
707                                                  chat);
708                 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
709                 g_signal_handlers_unblock_by_func (buffer,
710                                                    chat_input_text_buffer_changed_cb,
711                                                    chat);
712
713                 return TRUE;
714         }
715
716         /* Catch enter but not ctrl/shift-enter */
717         if (IS_ENTER (event->keyval) &&
718             !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
719                 GtkTextView *view;
720
721                 /* This is to make sure that kinput2 gets the enter. And if
722                  * it's handled there we shouldn't send on it. This is because
723                  * kinput2 uses Enter to commit letters. See:
724                  * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
725                  */
726
727                 view = GTK_TEXT_VIEW (chat->input_text_view);
728                 if (gtk_im_context_filter_keypress (view->im_context, event)) {
729                         GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
730                         return TRUE;
731                 }
732
733                 chat_input_text_view_send (chat);
734                 return TRUE;
735         }
736
737         text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
738
739         if (IS_ENTER (event->keyval) &&
740             (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
741                 /* Newline for shift/control-enter. */
742                 return FALSE;
743         }
744         if (!(event->state & GDK_CONTROL_MASK) &&
745             event->keyval == GDK_Page_Up) {
746                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
747                 gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) - gtk_adjustment_get_page_size (adj));
748                 return TRUE;
749         }
750         if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
751             event->keyval == GDK_Page_Down) {
752                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
753                 val = MIN (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj),
754                            gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
755                 gtk_adjustment_set_value (adj, val);
756                 return TRUE;
757         }
758         if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) &&
759             event->keyval == GDK_Tab) {
760                 GtkTextBuffer *buffer;
761                 GtkTextIter    start, current;
762                 gchar         *nick, *completed;
763                 GList         *list, *completed_list;
764                 gboolean       is_start_of_buffer;
765
766                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (EMPATHY_CHAT (chat)->input_text_view));
767                 gtk_text_buffer_get_iter_at_mark (buffer, &current, gtk_text_buffer_get_insert (buffer));
768
769                 /* Get the start of the nick to complete. */
770                 gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
771                 gtk_text_iter_backward_word_start (&start);
772                 is_start_of_buffer = gtk_text_iter_is_start (&start);
773
774                 list = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (priv->tp_chat));
775                 g_completion_add_items (priv->completion, list);
776
777                 nick = gtk_text_buffer_get_text (buffer, &start, &current, FALSE);
778                 completed_list = g_completion_complete (priv->completion,
779                                                         nick,
780                                                         &completed);
781
782                 g_free (nick);
783
784                 if (completed) {
785                         guint        len;
786                         const gchar *text;
787                         gchar       *complete_char = NULL;
788
789                         gtk_text_buffer_delete (buffer, &start, &current);
790
791                         len = g_list_length (completed_list);
792
793                         if (len == 1) {
794                                 /* If we only have one hit, use that text
795                                  * instead of the text in completed since the
796                                  * completed text will use the typed string
797                                  * which might be cased all wrong.
798                                  * Fixes #120876
799                                  * */
800                                 text = empathy_contact_get_name (completed_list->data);
801                         } else {
802                                 text = completed;
803                         }
804
805                         gtk_text_buffer_insert_at_cursor (buffer, text, strlen (text));
806
807                         if (len == 1 && is_start_of_buffer &&
808                             empathy_conf_get_string (empathy_conf_get (),
809                                                      EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR,
810                                                      &complete_char) &&
811                             complete_char != NULL) {
812                                 gtk_text_buffer_insert_at_cursor (buffer,
813                                                                   complete_char,
814                                                                   strlen (complete_char));
815                                 gtk_text_buffer_insert_at_cursor (buffer, " ", 1);
816                                 g_free (complete_char);
817                         }
818
819                         g_free (completed);
820                 }
821
822                 g_completion_clear_items (priv->completion);
823
824                 g_list_foreach (list, (GFunc) g_object_unref, NULL);
825                 g_list_free (list);
826
827                 return TRUE;
828         }
829
830         return FALSE;
831 }
832
833 static gboolean
834 chat_text_view_focus_in_event_cb (GtkWidget  *widget,
835                                   GdkEvent   *event,
836                                   EmpathyChat *chat)
837 {
838         gtk_widget_grab_focus (chat->input_text_view);
839
840         return TRUE;
841 }
842
843 static gboolean
844 chat_input_set_size_request_idle (gpointer sw)
845 {
846         gtk_widget_set_size_request (sw, -1, MAX_INPUT_HEIGHT);
847
848         return FALSE;
849 }
850
851 static void
852 chat_input_size_request_cb (GtkWidget      *widget,
853                             GtkRequisition *requisition,
854                             EmpathyChat    *chat)
855 {
856         EmpathyChatPriv *priv = GET_PRIV (chat);
857         GtkWidget       *sw;
858
859         sw = gtk_widget_get_parent (widget);
860         if (requisition->height >= MAX_INPUT_HEIGHT && !priv->has_input_vscroll) {
861                 g_idle_add (chat_input_set_size_request_idle, sw);
862                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
863                                                 GTK_POLICY_NEVER,
864                                                 GTK_POLICY_ALWAYS);
865                 priv->has_input_vscroll = TRUE;
866         }
867
868         if (requisition->height < MAX_INPUT_HEIGHT && priv->has_input_vscroll) {
869                 gtk_widget_set_size_request (sw, -1, -1);
870                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
871                                                 GTK_POLICY_NEVER,
872                                                 GTK_POLICY_NEVER);
873                 priv->has_input_vscroll = FALSE;
874         }
875 }
876
877 static void
878 chat_input_realize_cb (GtkWidget   *widget,
879                        EmpathyChat *chat)
880 {
881         DEBUG ("Setting focus to the input text view");
882         gtk_widget_grab_focus (widget);
883 }
884
885 static void
886 chat_insert_smiley_activate_cb (EmpathySmileyManager *manager,
887                                 EmpathySmiley        *smiley,
888                                 gpointer              user_data)
889 {
890         EmpathyChat   *chat = EMPATHY_CHAT (user_data);
891         GtkTextBuffer *buffer;
892         GtkTextIter    iter;
893
894         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
895
896         gtk_text_buffer_get_end_iter (buffer, &iter);
897         gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
898
899         gtk_text_buffer_get_end_iter (buffer, &iter);
900         gtk_text_buffer_insert (buffer, &iter, " ", -1);
901 }
902
903 typedef struct {
904         EmpathyChat  *chat;
905         gchar       *word;
906
907         GtkTextIter  start;
908         GtkTextIter  end;
909 } EmpathyChatSpell;
910
911 static EmpathyChatSpell *
912 chat_spell_new (EmpathyChat  *chat,
913                 const gchar *word,
914                 GtkTextIter  start,
915                 GtkTextIter  end)
916 {
917         EmpathyChatSpell *chat_spell;
918
919         chat_spell = g_slice_new0 (EmpathyChatSpell);
920
921         chat_spell->chat = g_object_ref (chat);
922         chat_spell->word = g_strdup (word);
923         chat_spell->start = start;
924         chat_spell->end = end;
925
926         return chat_spell;
927 }
928
929 static void
930 chat_spell_free (EmpathyChatSpell *chat_spell)
931 {
932         g_object_unref (chat_spell->chat);
933         g_free (chat_spell->word);
934         g_slice_free (EmpathyChatSpell, chat_spell);
935 }
936
937 static void
938 chat_spelling_menu_activate_cb (GtkMenuItem     *menu_item,
939                                                 EmpathyChatSpell *chat_spell)
940 {
941     empathy_chat_correct_word (chat_spell->chat,
942                                &(chat_spell->start),
943                                &(chat_spell->end),
944                                gtk_menu_item_get_label (menu_item));
945 }
946
947 static GtkWidget *
948 chat_spelling_build_menu (EmpathyChatSpell *chat_spell)
949 {
950     GtkWidget *menu, *menu_item;
951     GList     *suggestions, *l;
952
953     menu = gtk_menu_new ();
954     suggestions = empathy_spell_get_suggestions (chat_spell->word);
955     if (suggestions == NULL) {
956         menu_item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
957         gtk_widget_set_sensitive (menu_item, FALSE);
958         gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
959     } else {
960         for (l = suggestions; l; l = l->next) {
961             menu_item = gtk_menu_item_new_with_label (l->data);
962             g_signal_connect (G_OBJECT (menu_item),
963                           "activate",
964                           G_CALLBACK (chat_spelling_menu_activate_cb),
965                           chat_spell);
966             gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
967         }
968     }
969     empathy_spell_free_suggestions (suggestions);
970
971     gtk_widget_show_all (menu);
972
973     return menu;
974 }
975
976 static void
977 chat_text_send_cb (GtkMenuItem *menuitem,
978                    EmpathyChat *chat)
979 {
980         chat_input_text_view_send (chat);
981 }
982
983 static void
984 chat_input_populate_popup_cb (GtkTextView *view,
985                               GtkMenu     *menu,
986                               EmpathyChat *chat)
987 {
988         EmpathyChatPriv      *priv;
989         GtkTextBuffer        *buffer;
990         GtkTextTagTable      *table;
991         GtkTextTag           *tag;
992         gint                  x, y;
993         GtkTextIter           iter, start, end;
994         GtkWidget            *item;
995         gchar                *str = NULL;
996         EmpathyChatSpell     *chat_spell;
997         GtkWidget            *spell_menu;
998         EmpathySmileyManager *smiley_manager;
999         GtkWidget            *smiley_menu;
1000         GtkWidget            *image;
1001
1002         priv = GET_PRIV (chat);
1003         buffer = gtk_text_view_get_buffer (view);
1004
1005         /* Add the emoticon menu. */
1006         item = gtk_separator_menu_item_new ();
1007         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1008         gtk_widget_show (item);
1009
1010         item = gtk_image_menu_item_new_with_mnemonic (_("Insert Smiley"));
1011         image = gtk_image_new_from_icon_name ("face-smile",
1012                                               GTK_ICON_SIZE_MENU);
1013         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1014         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1015         gtk_widget_show (item);
1016
1017         smiley_manager = empathy_smiley_manager_dup_singleton ();
1018         smiley_menu = empathy_smiley_menu_new (smiley_manager,
1019                                                chat_insert_smiley_activate_cb,
1020                                                chat);
1021         gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
1022         g_object_unref (smiley_manager);
1023
1024         /* Add the Send menu item. */
1025         gtk_text_buffer_get_bounds (buffer, &start, &end);
1026         str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1027         if (!EMP_STR_EMPTY (str)) {
1028                 item = gtk_menu_item_new_with_mnemonic (_("_Send"));
1029                 g_signal_connect (G_OBJECT (item), "activate",
1030                                   G_CALLBACK (chat_text_send_cb), chat);
1031                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1032                 gtk_widget_show (item);
1033         }
1034         str = NULL;
1035
1036         /* Add the spell check menu item. */
1037         table = gtk_text_buffer_get_tag_table (buffer);
1038         tag = gtk_text_tag_table_lookup (table, "misspelled");
1039         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
1040         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
1041                                                GTK_TEXT_WINDOW_WIDGET,
1042                                                x, y,
1043                                                &x, &y);
1044         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
1045         start = end = iter;
1046         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
1047             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
1048
1049                 str = gtk_text_buffer_get_text (buffer,
1050                                                 &start, &end, FALSE);
1051         }
1052         if (!EMP_STR_EMPTY (str)) {
1053                 chat_spell = chat_spell_new (chat, str, start, end);
1054                 g_object_set_data_full (G_OBJECT (menu),
1055                                         "chat_spell", chat_spell,
1056                                         (GDestroyNotify) chat_spell_free);
1057
1058                 item = gtk_separator_menu_item_new ();
1059                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1060                 gtk_widget_show (item);
1061
1062                 item = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
1063                 image = gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK,
1064                                                       GTK_ICON_SIZE_MENU);
1065                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1066
1067                 spell_menu = chat_spelling_build_menu (chat_spell);
1068                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), spell_menu);
1069
1070                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1071                 gtk_widget_show (item);
1072         }
1073 }
1074
1075 static gboolean
1076 chat_log_filter (EmpathyMessage *message,
1077                  gpointer user_data)
1078 {
1079         EmpathyChat *chat = (EmpathyChat *) user_data;
1080         EmpathyChatPriv *priv = GET_PRIV (chat);
1081         const GList *pending;
1082
1083         pending = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1084
1085         for (; pending; pending = g_list_next (pending)) {
1086                 if (empathy_message_equal (message, pending->data)) {
1087                         return FALSE;
1088                 }
1089         }
1090
1091         return TRUE;
1092 }
1093
1094 static void
1095 chat_add_logs (EmpathyChat *chat)
1096 {
1097         EmpathyChatPriv *priv = GET_PRIV (chat);
1098         gboolean         is_chatroom;
1099         GList           *messages, *l;
1100
1101         if (!priv->id) {
1102                 return;
1103         }
1104
1105         /* Turn off scrolling temporarily */
1106         empathy_chat_view_scroll (chat->view, FALSE);
1107
1108         /* Add messages from last conversation */
1109         is_chatroom = priv->handle_type == TP_HANDLE_TYPE_ROOM;
1110
1111         messages = empathy_log_manager_get_filtered_messages (priv->log_manager,
1112                                                               priv->account,
1113                                                               priv->id,
1114                                                               is_chatroom,
1115                                                               5,
1116                                                               chat_log_filter,
1117                                                               chat);
1118
1119         for (l = messages; l; l = g_list_next (l)) {
1120                 empathy_chat_view_append_message (chat->view, l->data);
1121                 g_object_unref (l->data);
1122         }
1123
1124         g_list_free (messages);
1125
1126         /* Turn back on scrolling */
1127         empathy_chat_view_scroll (chat->view, TRUE);
1128 }
1129
1130 static gint
1131 chat_contacts_completion_func (const gchar *s1,
1132                                const gchar *s2,
1133                                gsize        n)
1134 {
1135         gchar *tmp, *nick1, *nick2;
1136         gint   ret;
1137
1138         if (s1 == s2) {
1139                 return 0;
1140         }
1141         if (!s1 || !s2) {
1142                 return s1 ? -1 : +1;
1143         }
1144
1145         tmp = g_utf8_normalize (s1, -1, G_NORMALIZE_DEFAULT);
1146         nick1 = g_utf8_casefold (tmp, -1);
1147         g_free (tmp);
1148
1149         tmp = g_utf8_normalize (s2, -1, G_NORMALIZE_DEFAULT);
1150         nick2 = g_utf8_casefold (tmp, -1);
1151         g_free (tmp);
1152
1153         ret = strncmp (nick1, nick2, n);
1154
1155         g_free (nick1);
1156         g_free (nick2);
1157
1158         return ret;
1159 }
1160
1161 static gchar *
1162 build_part_message (guint           reason,
1163                     const gchar    *name,
1164                     EmpathyContact *actor,
1165                     const gchar    *message)
1166 {
1167         GString *s = g_string_new ("");
1168         const gchar *actor_name = NULL;
1169
1170         if (actor != NULL) {
1171                 actor_name = empathy_contact_get_name (actor);
1172         }
1173
1174         /* Having an actor only really makes sense for a few actions... */
1175         switch (reason) {
1176         case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
1177                 g_string_append_printf (s, _("%s has disconnected"), name);
1178                 break;
1179         case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED:
1180                 if (actor_name != NULL) {
1181                         /* translators: reverse the order of these arguments
1182                          * if the kicked should come before the kicker in your locale.
1183                          */
1184                         g_string_append_printf (s, _("%1$s was kicked by %2$s"),
1185                                 name, actor_name);
1186                 } else {
1187                         g_string_append_printf (s, _("%s was kicked"), name);
1188                 }
1189                 break;
1190         case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
1191                 if (actor_name != NULL) {
1192                         /* translators: reverse the order of these arguments
1193                          * if the banned should come before the banner in your locale.
1194                          */
1195                         g_string_append_printf (s, _("%1$s was banned by %2$s"),
1196                                 name, actor_name);
1197                 } else {
1198                         g_string_append_printf (s, _("%s was banned"), name);
1199                 }
1200                 break;
1201         default:
1202                 g_string_append_printf (s, _("%s has left the room"), name);
1203         }
1204
1205         if (!EMP_STR_EMPTY (message)) {
1206                 /* Note to translators: this string is appended to
1207                  * notifications like "foo has left the room", with the message
1208                  * given by the user living the room. If this poses a problem,
1209                  * please let us know. :-)
1210                  */
1211                 g_string_append_printf (s, _(" (%s)"), message);
1212         }
1213
1214         return g_string_free (s, FALSE);
1215 }
1216
1217 static void
1218 chat_members_changed_cb (EmpathyTpChat  *tp_chat,
1219                          EmpathyContact *contact,
1220                          EmpathyContact *actor,
1221                          guint           reason,
1222                          gchar          *message,
1223                          gboolean        is_member,
1224                          EmpathyChat    *chat)
1225 {
1226         EmpathyChatPriv *priv = GET_PRIV (chat);
1227         const gchar *name = empathy_contact_get_name (contact);
1228         gchar *str;
1229
1230         if (priv->block_events_timeout_id != 0)
1231                 return;
1232
1233         if (is_member) {
1234                 str = g_strdup_printf (_("%s has joined the room"),
1235                                        name);
1236         } else {
1237                 str = build_part_message (reason, name, actor, message);
1238         }
1239
1240         empathy_chat_view_append_event (chat->view, str);
1241         g_free (str);
1242 }
1243
1244 static gboolean
1245 chat_reset_size_request (gpointer widget)
1246 {
1247         gtk_widget_set_size_request (widget, -1, -1);
1248
1249         return FALSE;
1250 }
1251
1252 static void
1253 chat_update_contacts_visibility (EmpathyChat *chat)
1254 {
1255         EmpathyChatPriv *priv = GET_PRIV (chat);
1256         gboolean show;
1257
1258         show = priv->remote_contact == NULL && priv->show_contacts;
1259
1260         if (!priv->scrolled_window_contacts) {
1261                 return;
1262         }
1263
1264         if (show && priv->contact_list_view == NULL) {
1265                 EmpathyContactListStore *store;
1266                 gint                     min_width;
1267
1268                 /* We are adding the contact list to the chat, we don't want the
1269                  * chat view to become too small. If the chat view is already
1270                  * smaller than 250 make sure that size won't change. If the
1271                  * chat view is bigger the contact list will take some space on
1272                  * it but we make sure the chat view don't become smaller than
1273                  * 250. Relax the size request once the resize is done */
1274                 min_width = MIN (priv->vbox_left->allocation.width, 250);
1275                 gtk_widget_set_size_request (priv->vbox_left, min_width, -1);
1276                 g_idle_add (chat_reset_size_request, priv->vbox_left);
1277
1278                 if (priv->contacts_width > 0) {
1279                         gtk_paned_set_position (GTK_PANED (priv->hpaned),
1280                                                 priv->contacts_width);
1281                 }
1282
1283                 store = empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
1284                 priv->contact_list_view = GTK_WIDGET (empathy_contact_list_view_new (store,
1285                         EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP,
1286                         EMPATHY_CONTACT_FEATURE_CHAT |
1287                         EMPATHY_CONTACT_FEATURE_CALL |
1288                         EMPATHY_CONTACT_FEATURE_LOG |
1289                         EMPATHY_CONTACT_FEATURE_INFO));
1290                 gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
1291                                    priv->contact_list_view);
1292                 gtk_widget_show (priv->contact_list_view);
1293                 gtk_widget_show (priv->scrolled_window_contacts);
1294                 g_object_unref (store);
1295         } else if (!show) {
1296                 priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
1297                 gtk_widget_hide (priv->scrolled_window_contacts);
1298                 if (priv->contact_list_view != NULL) {
1299                         gtk_widget_destroy (priv->contact_list_view);
1300                         priv->contact_list_view = NULL;
1301                 }
1302         }
1303 }
1304
1305 void
1306 empathy_chat_set_show_contacts (EmpathyChat *chat,
1307                                 gboolean     show)
1308 {
1309         EmpathyChatPriv *priv = GET_PRIV (chat);
1310
1311         priv->show_contacts = show;
1312
1313         chat_update_contacts_visibility (chat);
1314
1315         g_object_notify (G_OBJECT (chat), "show-contacts");
1316 }
1317
1318 static void
1319 chat_remote_contact_changed_cb (EmpathyChat *chat)
1320 {
1321         EmpathyChatPriv *priv = GET_PRIV (chat);
1322
1323         if (priv->remote_contact != NULL) {
1324                 g_object_unref (priv->remote_contact);
1325                 priv->remote_contact = NULL;
1326         }
1327
1328         priv->remote_contact = empathy_tp_chat_get_remote_contact (priv->tp_chat);
1329         if (priv->remote_contact != NULL) {
1330                 g_object_ref (priv->remote_contact);
1331                 priv->handle_type = TP_HANDLE_TYPE_CONTACT;
1332                 g_free (priv->id);
1333                 priv->id = g_strdup (empathy_contact_get_id (priv->remote_contact));
1334         }
1335         else if (priv->tp_chat != NULL) {
1336                 TpChannel *channel;
1337
1338                 channel = empathy_tp_chat_get_channel (priv->tp_chat);
1339                 g_object_get (channel, "handle-type", &priv->handle_type, NULL);
1340                 g_free (priv->id);
1341                 priv->id = g_strdup (empathy_tp_chat_get_id (priv->tp_chat));
1342         }
1343
1344         chat_update_contacts_visibility (chat);
1345
1346         g_object_notify (G_OBJECT (chat), "remote-contact");
1347         g_object_notify (G_OBJECT (chat), "id");
1348 }
1349
1350 static void
1351 chat_destroy_cb (EmpathyTpChat *tp_chat,
1352                  EmpathyChat   *chat)
1353 {
1354         EmpathyChatPriv *priv;
1355
1356         priv = GET_PRIV (chat);
1357
1358         if (!priv->tp_chat) {
1359                 return;
1360         }
1361
1362         chat_composing_remove_timeout (chat);
1363         g_object_unref (priv->tp_chat);
1364         priv->tp_chat = NULL;
1365         g_object_notify (G_OBJECT (chat), "tp-chat");
1366
1367         empathy_chat_view_append_event (chat->view, _("Disconnected"));
1368         gtk_widget_set_sensitive (chat->input_text_view, FALSE);
1369         empathy_chat_set_show_contacts (chat, FALSE);
1370 }
1371
1372 static void
1373 show_pending_messages (EmpathyChat *chat) {
1374         EmpathyChatPriv *priv = GET_PRIV (chat);
1375         const GList *messages, *l;
1376
1377         if (chat->view == NULL || priv->tp_chat == NULL)
1378                 return;
1379
1380         messages = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1381
1382         for (l = messages; l != NULL ; l = g_list_next (l)) {
1383                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1384                 chat_message_received (chat, message);
1385         }
1386         empathy_tp_chat_acknowledge_messages (priv->tp_chat, messages);
1387 }
1388
1389 static void
1390 chat_create_ui (EmpathyChat *chat)
1391 {
1392         EmpathyChatPriv *priv = GET_PRIV (chat);
1393         GtkBuilder      *gui;
1394         GList           *list = NULL;
1395         gchar           *filename;
1396         GtkTextBuffer   *buffer;
1397
1398         filename = empathy_file_lookup ("empathy-chat.ui",
1399                                         "libempathy-gtk");
1400         gui = empathy_builder_get_file (filename,
1401                                         "chat_widget", &priv->widget,
1402                                         "hpaned", &priv->hpaned,
1403                                         "vbox_left", &priv->vbox_left,
1404                                         "scrolled_window_chat", &priv->scrolled_window_chat,
1405                                         "scrolled_window_input", &priv->scrolled_window_input,
1406                                         "hbox_topic", &priv->hbox_topic,
1407                                         "label_topic", &priv->label_topic,
1408                                         "scrolled_window_contacts", &priv->scrolled_window_contacts,
1409                                         NULL);
1410         g_free (filename);
1411
1412         /* Add message view. */
1413         chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
1414         g_signal_connect (chat->view, "focus_in_event",
1415                           G_CALLBACK (chat_text_view_focus_in_event_cb),
1416                           chat);
1417         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_chat),
1418                            GTK_WIDGET (chat->view));
1419         gtk_widget_show (GTK_WIDGET (chat->view));
1420
1421         /* Add input GtkTextView */
1422         chat->input_text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
1423                                               "pixels-above-lines", 2,
1424                                               "pixels-below-lines", 2,
1425                                               "pixels-inside-wrap", 1,
1426                                               "right-margin", 2,
1427                                               "left-margin", 2,
1428                                               "wrap-mode", GTK_WRAP_WORD_CHAR,
1429                                               NULL);
1430         g_signal_connect (chat->input_text_view, "key-press-event",
1431                           G_CALLBACK (chat_input_key_press_event_cb),
1432                           chat);
1433         g_signal_connect (chat->input_text_view, "size-request",
1434                           G_CALLBACK (chat_input_size_request_cb),
1435                           chat);
1436         g_signal_connect (chat->input_text_view, "realize",
1437                           G_CALLBACK (chat_input_realize_cb),
1438                           chat);
1439         g_signal_connect (chat->input_text_view, "populate-popup",
1440                           G_CALLBACK (chat_input_populate_popup_cb),
1441                           chat);
1442         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1443         g_signal_connect (buffer, "changed",
1444                           G_CALLBACK (chat_input_text_buffer_changed_cb),
1445                           chat);
1446         gtk_text_buffer_create_tag (buffer, "misspelled",
1447                                     "underline", PANGO_UNDERLINE_ERROR,
1448                                     NULL);
1449         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
1450                            chat->input_text_view);
1451         gtk_widget_show (chat->input_text_view);
1452
1453         /* Create contact list */
1454         chat_update_contacts_visibility (chat);
1455
1456         /* Initialy hide the topic, will be shown if not empty */
1457         gtk_widget_hide (priv->hbox_topic);
1458
1459         /* Set widget focus order */
1460         list = g_list_append (NULL, priv->scrolled_window_input);
1461         gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
1462         g_list_free (list);
1463
1464         list = g_list_append (NULL, priv->vbox_left);
1465         list = g_list_append (list, priv->scrolled_window_contacts);
1466         gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
1467         g_list_free (list);
1468
1469         list = g_list_append (NULL, priv->hpaned);
1470         list = g_list_append (list, priv->hbox_topic);
1471         gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
1472         g_list_free (list);
1473
1474         /* Add the main widget in the chat widget */
1475         gtk_container_add (GTK_CONTAINER (chat), priv->widget);
1476         g_object_unref (gui);
1477 }
1478
1479 static void
1480 chat_size_request (GtkWidget      *widget,
1481                    GtkRequisition *requisition)
1482 {
1483   GtkBin *bin = GTK_BIN (widget);
1484   GtkWidget *child;
1485
1486   requisition->width = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
1487   requisition->height = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
1488
1489   child = gtk_bin_get_child (bin);
1490
1491   if (child && GTK_WIDGET_VISIBLE (child))
1492     {
1493       GtkRequisition child_requisition;
1494
1495       gtk_widget_size_request (child, &child_requisition);
1496
1497       requisition->width += child_requisition.width;
1498       requisition->height += child_requisition.height;
1499     }
1500 }
1501
1502 static void
1503 chat_size_allocate (GtkWidget     *widget,
1504                     GtkAllocation *allocation)
1505 {
1506   GtkBin *bin = GTK_BIN (widget);
1507   GtkAllocation child_allocation;
1508   GtkWidget *child;
1509
1510   widget->allocation = *allocation;
1511
1512   child = gtk_bin_get_child (bin);
1513
1514   if (child && GTK_WIDGET_VISIBLE (child))
1515     {
1516       child_allocation.x = allocation->x + gtk_container_get_border_width (GTK_CONTAINER (widget));
1517       child_allocation.y = allocation->y + gtk_container_get_border_width (GTK_CONTAINER (widget));
1518       child_allocation.width = MAX (allocation->width - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
1519       child_allocation.height = MAX (allocation->height - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
1520
1521       gtk_widget_size_allocate (child, &child_allocation);
1522     }
1523 }
1524
1525 static void
1526 chat_finalize (GObject *object)
1527 {
1528         EmpathyChat     *chat;
1529         EmpathyChatPriv *priv;
1530
1531         chat = EMPATHY_CHAT (object);
1532         priv = GET_PRIV (chat);
1533
1534         DEBUG ("Finalized: %p", object);
1535
1536         g_slist_foreach (priv->sent_messages, (GFunc) g_free, NULL);
1537         g_slist_free (priv->sent_messages);
1538
1539         g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
1540         g_list_free (priv->compositors);
1541
1542         chat_composing_remove_timeout (chat);
1543
1544         g_signal_handlers_disconnect_by_func (priv->account_manager,
1545                                               chat_new_connection_cb, object);
1546
1547         g_object_unref (priv->account_manager);
1548         g_object_unref (priv->log_manager);
1549
1550         if (priv->tp_chat) {
1551                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1552                         chat_destroy_cb, chat);
1553                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1554                         chat_message_received_cb, chat);
1555                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1556                         chat_send_error_cb, chat);
1557                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1558                         chat_state_changed_cb, chat);
1559                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1560                         chat_property_changed_cb, chat);
1561                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1562                         chat_members_changed_cb, chat);
1563                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1564                         chat_remote_contact_changed_cb, chat);
1565                 empathy_tp_chat_close (priv->tp_chat);
1566                 g_object_unref (priv->tp_chat);
1567         }
1568         if (priv->account) {
1569                 g_object_unref (priv->account);
1570         }
1571         if (priv->remote_contact) {
1572                 g_object_unref (priv->remote_contact);
1573         }
1574
1575         if (priv->block_events_timeout_id) {
1576                 g_source_remove (priv->block_events_timeout_id);
1577         }
1578
1579         g_free (priv->id);
1580         g_free (priv->name);
1581         g_free (priv->subject);
1582         g_completion_free (priv->completion);
1583
1584         G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
1585 }
1586
1587 static void
1588 chat_constructed (GObject *object)
1589 {
1590         EmpathyChat *chat = EMPATHY_CHAT (object);
1591
1592         chat_create_ui (chat);
1593         chat_add_logs (chat);
1594         show_pending_messages (chat);
1595 }
1596
1597 static void
1598 empathy_chat_class_init (EmpathyChatClass *klass)
1599 {
1600         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1601         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
1602
1603         object_class->finalize = chat_finalize;
1604         object_class->get_property = chat_get_property;
1605         object_class->set_property = chat_set_property;
1606         object_class->constructed = chat_constructed;
1607
1608         widget_class->size_request = chat_size_request;
1609         widget_class->size_allocate = chat_size_allocate;
1610
1611         g_object_class_install_property (object_class,
1612                                          PROP_TP_CHAT,
1613                                          g_param_spec_object ("tp-chat",
1614                                                               "Empathy tp chat",
1615                                                               "The tp chat object",
1616                                                               EMPATHY_TYPE_TP_CHAT,
1617                                                               G_PARAM_CONSTRUCT |
1618                                                               G_PARAM_READWRITE |
1619                                                               G_PARAM_STATIC_STRINGS));
1620         g_object_class_install_property (object_class,
1621                                          PROP_ACCOUNT,
1622                                          g_param_spec_object ("account",
1623                                                               "Account of the chat",
1624                                                               "The account of the chat",
1625                                                               EMPATHY_TYPE_ACCOUNT,
1626                                                               G_PARAM_READABLE |
1627                                                               G_PARAM_STATIC_STRINGS));
1628         g_object_class_install_property (object_class,
1629                                          PROP_ID,
1630                                          g_param_spec_string ("id",
1631                                                               "Chat's id",
1632                                                               "The id of the chat",
1633                                                               NULL,
1634                                                               G_PARAM_READABLE |
1635                                                               G_PARAM_STATIC_STRINGS));
1636         g_object_class_install_property (object_class,
1637                                          PROP_NAME,
1638                                          g_param_spec_string ("name",
1639                                                               "Chat's name",
1640                                                               "The name of the chat",
1641                                                               NULL,
1642                                                               G_PARAM_READABLE |
1643                                                               G_PARAM_STATIC_STRINGS));
1644         g_object_class_install_property (object_class,
1645                                          PROP_SUBJECT,
1646                                          g_param_spec_string ("subject",
1647                                                               "Chat's subject",
1648                                                               "The subject or topic of the chat",
1649                                                               NULL,
1650                                                               G_PARAM_READABLE |
1651                                                               G_PARAM_STATIC_STRINGS));
1652         g_object_class_install_property (object_class,
1653                                          PROP_REMOTE_CONTACT,
1654                                          g_param_spec_object ("remote-contact",
1655                                                               "The remote contact",
1656                                                               "The remote contact is any",
1657                                                               EMPATHY_TYPE_CONTACT,
1658                                                               G_PARAM_READABLE |
1659                                                               G_PARAM_STATIC_STRINGS));
1660         g_object_class_install_property (object_class,
1661                                          PROP_SHOW_CONTACTS,
1662                                          g_param_spec_boolean ("show-contacts",
1663                                                                "Contacts' visibility",
1664                                                                "The visibility of the contacts' list",
1665                                                                TRUE,
1666                                                                G_PARAM_READWRITE |
1667                                                                G_PARAM_STATIC_STRINGS));
1668
1669         signals[COMPOSING] =
1670                 g_signal_new ("composing",
1671                               G_OBJECT_CLASS_TYPE (object_class),
1672                               G_SIGNAL_RUN_LAST,
1673                               0,
1674                               NULL, NULL,
1675                               g_cclosure_marshal_VOID__BOOLEAN,
1676                               G_TYPE_NONE,
1677                               1, G_TYPE_BOOLEAN);
1678
1679         signals[NEW_MESSAGE] =
1680                 g_signal_new ("new-message",
1681                               G_OBJECT_CLASS_TYPE (object_class),
1682                               G_SIGNAL_RUN_LAST,
1683                               0,
1684                               NULL, NULL,
1685                               g_cclosure_marshal_VOID__OBJECT,
1686                               G_TYPE_NONE,
1687                               1, EMPATHY_TYPE_MESSAGE);
1688
1689         g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
1690 }
1691
1692 static gboolean
1693 chat_block_events_timeout_cb (gpointer data)
1694 {
1695         EmpathyChatPriv *priv = GET_PRIV (data);
1696
1697         priv->block_events_timeout_id = 0;
1698
1699         return FALSE;
1700 }
1701
1702 static void
1703 empathy_chat_init (EmpathyChat *chat)
1704 {
1705         EmpathyChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
1706                 EMPATHY_TYPE_CHAT, EmpathyChatPriv);
1707
1708         chat->priv = priv;
1709         priv->log_manager = empathy_log_manager_dup_singleton ();
1710         priv->contacts_width = -1;
1711         priv->sent_messages = NULL;
1712         priv->sent_messages_index = -1;
1713         priv->account_manager = empathy_account_manager_dup_singleton ();
1714
1715         g_signal_connect (priv->account_manager,
1716                           "new-connection",
1717                           G_CALLBACK (chat_new_connection_cb),
1718                           chat);
1719
1720         empathy_conf_get_bool (empathy_conf_get (),
1721                                EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS,
1722                                &priv->show_contacts);
1723
1724         /* Block events for some time to avoid having "has come online" or
1725          * "joined" messages. */
1726         priv->block_events_timeout_id =
1727                 g_timeout_add_seconds (1, chat_block_events_timeout_cb, chat);
1728
1729         /* Add nick name completion */
1730         priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_name);
1731         g_completion_set_compare (priv->completion, chat_contacts_completion_func);
1732 }
1733
1734 EmpathyChat *
1735 empathy_chat_new (EmpathyTpChat *tp_chat)
1736 {
1737         return g_object_new (EMPATHY_TYPE_CHAT, "tp-chat", tp_chat, NULL);
1738 }
1739
1740 EmpathyTpChat *
1741 empathy_chat_get_tp_chat (EmpathyChat *chat)
1742 {
1743         EmpathyChatPriv *priv = GET_PRIV (chat);
1744
1745         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1746
1747         return priv->tp_chat;
1748 }
1749
1750 void
1751 empathy_chat_set_tp_chat (EmpathyChat   *chat,
1752                           EmpathyTpChat *tp_chat)
1753 {
1754         EmpathyChatPriv *priv = GET_PRIV (chat);
1755         TpConnection    *connection;
1756
1757         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1758         g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
1759         g_return_if_fail (empathy_tp_chat_is_ready (tp_chat));
1760
1761         if (priv->tp_chat) {
1762                 return;
1763         }
1764
1765         if (priv->account) {
1766                 g_object_unref (priv->account);
1767         }
1768
1769         priv->tp_chat = g_object_ref (tp_chat);
1770         connection = empathy_tp_chat_get_connection (priv->tp_chat);
1771         priv->account = empathy_account_manager_get_account_for_connection (
1772                                                              priv->account_manager,
1773                                                              connection);
1774         g_object_ref (priv->account);
1775
1776         g_signal_connect (tp_chat, "destroy",
1777                           G_CALLBACK (chat_destroy_cb),
1778                           chat);
1779         g_signal_connect (tp_chat, "message-received",
1780                           G_CALLBACK (chat_message_received_cb),
1781                           chat);
1782         g_signal_connect (tp_chat, "send-error",
1783                           G_CALLBACK (chat_send_error_cb),
1784                           chat);
1785         g_signal_connect (tp_chat, "chat-state-changed",
1786                           G_CALLBACK (chat_state_changed_cb),
1787                           chat);
1788         g_signal_connect (tp_chat, "property-changed",
1789                           G_CALLBACK (chat_property_changed_cb),
1790                           chat);
1791         g_signal_connect (tp_chat, "members-changed",
1792                           G_CALLBACK (chat_members_changed_cb),
1793                           chat);
1794         g_signal_connect_swapped (tp_chat, "notify::remote-contact",
1795                                   G_CALLBACK (chat_remote_contact_changed_cb),
1796                                   chat);
1797
1798         chat_remote_contact_changed_cb (chat);
1799
1800         if (chat->input_text_view) {
1801                 gtk_widget_set_sensitive (chat->input_text_view, TRUE);
1802                 if (priv->block_events_timeout_id == 0) {
1803                         empathy_chat_view_append_event (chat->view, _("Connected"));
1804                 }
1805         }
1806
1807         g_object_notify (G_OBJECT (chat), "tp-chat");
1808         g_object_notify (G_OBJECT (chat), "id");
1809         g_object_notify (G_OBJECT (chat), "account");
1810
1811         /* This is a noop when tp-chat is set at object construction time and causes
1812          * the pending messages to be show when it's set on the object after it has
1813          * been created */
1814         show_pending_messages (chat);
1815 }
1816
1817 EmpathyAccount *
1818 empathy_chat_get_account (EmpathyChat *chat)
1819 {
1820         EmpathyChatPriv *priv = GET_PRIV (chat);
1821
1822         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1823
1824         return priv->account;
1825 }
1826
1827 const gchar *
1828 empathy_chat_get_id (EmpathyChat *chat)
1829 {
1830         EmpathyChatPriv *priv = GET_PRIV (chat);
1831
1832         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1833
1834         return priv->id;
1835 }
1836
1837 const gchar *
1838 empathy_chat_get_name (EmpathyChat *chat)
1839 {
1840         EmpathyChatPriv *priv = GET_PRIV (chat);
1841         const gchar *ret;
1842
1843         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1844
1845         ret = priv->name;
1846         if (!ret && priv->remote_contact) {
1847                 ret = empathy_contact_get_name (priv->remote_contact);
1848         }
1849
1850         if (!ret)
1851                 ret = priv->id;
1852
1853         return ret ? ret : _("Conversation");
1854 }
1855
1856 const gchar *
1857 empathy_chat_get_subject (EmpathyChat *chat)
1858 {
1859         EmpathyChatPriv *priv = GET_PRIV (chat);
1860
1861         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1862
1863         return priv->subject;
1864 }
1865
1866 EmpathyContact *
1867 empathy_chat_get_remote_contact (EmpathyChat *chat)
1868 {
1869         EmpathyChatPriv *priv = GET_PRIV (chat);
1870
1871         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1872
1873         return priv->remote_contact;
1874 }
1875
1876 GtkWidget *
1877 empathy_chat_get_contact_menu (EmpathyChat *chat)
1878 {
1879         EmpathyChatPriv *priv = GET_PRIV (chat);
1880         GtkWidget       *menu = NULL;
1881
1882         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1883
1884         if (priv->remote_contact) {
1885                 menu = empathy_contact_menu_new (priv->remote_contact,
1886                                                  EMPATHY_CONTACT_FEATURE_CALL |
1887                                                  EMPATHY_CONTACT_FEATURE_LOG |
1888                                                  EMPATHY_CONTACT_FEATURE_INFO);
1889         }
1890         else if (priv->contact_list_view) {
1891                 EmpathyContactListView *view;
1892
1893                 view = EMPATHY_CONTACT_LIST_VIEW (priv->contact_list_view);
1894                 menu = empathy_contact_list_view_get_contact_menu (view);
1895         }
1896
1897         return menu;
1898 }
1899
1900 void
1901 empathy_chat_clear (EmpathyChat *chat)
1902 {
1903         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1904
1905         empathy_chat_view_clear (chat->view);
1906 }
1907
1908 void
1909 empathy_chat_scroll_down (EmpathyChat *chat)
1910 {
1911         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1912
1913         empathy_chat_view_scroll_down (chat->view);
1914 }
1915
1916 void
1917 empathy_chat_cut (EmpathyChat *chat)
1918 {
1919         GtkTextBuffer *buffer;
1920
1921         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1922
1923         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1924         if (gtk_text_buffer_get_has_selection (buffer)) {
1925                 GtkClipboard *clipboard;
1926
1927                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1928
1929                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
1930         }
1931 }
1932
1933 void
1934 empathy_chat_copy (EmpathyChat *chat)
1935 {
1936         GtkTextBuffer *buffer;
1937
1938         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1939
1940         if (empathy_chat_view_get_has_selection (chat->view)) {
1941                 empathy_chat_view_copy_clipboard (chat->view);
1942                 return;
1943         }
1944
1945         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1946         if (gtk_text_buffer_get_has_selection (buffer)) {
1947                 GtkClipboard *clipboard;
1948
1949                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1950
1951                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
1952         }
1953 }
1954
1955 void
1956 empathy_chat_paste (EmpathyChat *chat)
1957 {
1958         GtkTextBuffer *buffer;
1959         GtkClipboard  *clipboard;
1960
1961         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1962
1963         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1964         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1965
1966         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
1967 }
1968
1969 void
1970 empathy_chat_correct_word (EmpathyChat  *chat,
1971                           GtkTextIter *start,
1972                           GtkTextIter *end,
1973                           const gchar *new_word)
1974 {
1975         GtkTextBuffer *buffer;
1976
1977         g_return_if_fail (chat != NULL);
1978         g_return_if_fail (new_word != NULL);
1979
1980         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1981
1982         gtk_text_buffer_delete (buffer, start, end);
1983         gtk_text_buffer_insert (buffer, start,
1984                                 new_word,
1985                                 -1);
1986 }
1987
1988 gboolean
1989 empathy_chat_is_room (EmpathyChat *chat)
1990 {
1991         EmpathyChatPriv *priv = GET_PRIV (chat);
1992
1993         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
1994
1995         return (priv->handle_type == TP_HANDLE_TYPE_ROOM);
1996 }
1997