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