]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat.c
edff73a3e9b452d031ba7bb0fee1d076cad794a5
[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 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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, 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.h>
35 #include <gtk/gtk.h>
36
37 #include <libempathy/empathy-contact-manager.h>
38 #include <libempathy/empathy-log-manager.h>
39 #include <libempathy/empathy-debug.h>
40 #include <libempathy/empathy-utils.h>
41 #include <libempathy/empathy-conf.h>
42 #include <libempathy/empathy-marshal.h>
43
44 #include "empathy-chat.h"
45 #include "empathy-chat-window.h"
46 #include "empathy-geometry.h"
47 #include "empathy-preferences.h"
48 #include "empathy-spell.h"
49 #include "empathy-spell-dialog.h"
50 #include "empathy-ui-utils.h"
51
52 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_CHAT, EmpathyChatPriv))
53
54 #define DEBUG_DOMAIN "Chat"
55
56 #define CHAT_DIR_CREATE_MODE  (S_IRUSR | S_IWUSR | S_IXUSR)
57 #define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
58
59 #define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
60
61 #define MAX_INPUT_HEIGHT 150
62
63 #define COMPOSING_STOP_TIMEOUT 5
64
65 struct _EmpathyChatPriv {
66         EmpathyLogManager     *log_manager;
67         EmpathyTpChat         *tp_chat;
68         EmpathyChatWindow      *window;
69         guint                  composing_stop_timeout_id;
70         gboolean               sensitive;
71         gchar                 *id;
72         GSList                *sent_messages;
73         gint                   sent_messages_index;
74         GList                 *compositors;
75         guint                  scroll_idle_id;
76         gboolean               first_tp_chat;
77         time_t                 last_log_timestamp;
78         /* Used to automatically shrink a window that has temporarily
79          * grown due to long input. 
80          */
81         gint                   padding_height;
82         gint                   default_window_height;
83         gint                   last_input_height;
84         gboolean               vscroll_visible;
85 };
86
87 typedef struct {
88         EmpathyChat  *chat;
89         gchar       *word;
90
91         GtkTextIter  start;
92         GtkTextIter  end;
93 } EmpathyChatSpell;
94
95 static void             empathy_chat_class_init           (EmpathyChatClass              *klass);
96 static void             empathy_chat_init                 (EmpathyChat                   *chat);
97 static void             chat_finalize                     (GObject                       *object);
98 static void             chat_destroy_cb                   (EmpathyTpChat                 *tp_chat,
99                                                            EmpathyChat                   *chat);
100 static void             chat_send                         (EmpathyChat                   *chat,
101                                                            const gchar                   *msg);
102 static void             chat_input_text_view_send         (EmpathyChat                   *chat);
103 static void             chat_message_received_cb          (EmpathyTpChat                 *tp_chat,
104                                                            EmpathyMessage                *message,
105                                                            EmpathyChat                   *chat);
106 static void             chat_send_error_cb                (EmpathyTpChat                 *tp_chat,
107                                                            EmpathyMessage                *message,
108                                                            TelepathyChannelTextSendError  error_code,
109                                                            EmpathyChat                   *chat);
110 void                    chat_sent_message_add             (EmpathyChat                   *chat,
111                                                            const gchar                   *str);
112 const gchar *           chat_sent_message_get_next        (EmpathyChat                   *chat);
113 const gchar *           chat_sent_message_get_last        (EmpathyChat                   *chat);
114 static gboolean         chat_input_key_press_event_cb     (GtkWidget                     *widget,
115                                                            GdkEventKey                   *event,
116                                                            EmpathyChat                   *chat);
117 static void             chat_input_text_buffer_changed_cb (GtkTextBuffer                 *buffer,
118                                                            EmpathyChat                   *chat);
119 static gboolean         chat_text_view_focus_in_event_cb  (GtkWidget                     *widget,
120                                                            GdkEvent                      *event,
121                                                            EmpathyChat                   *chat);
122 static void             chat_text_view_scroll_hide_cb     (GtkWidget                     *widget,
123                                                            EmpathyChat                   *chat);
124 static void             chat_text_view_size_allocate_cb   (GtkWidget                     *widget,
125                                                            GtkAllocation                 *allocation,
126                                                            EmpathyChat                   *chat);
127 static void             chat_text_view_realize_cb         (GtkWidget                     *widget,
128                                                            EmpathyChat                   *chat);
129 static void             chat_text_populate_popup_cb       (GtkTextView                   *view,
130                                                            GtkMenu                       *menu,
131                                                            EmpathyChat                   *chat);
132 static void             chat_text_check_word_spelling_cb  (GtkMenuItem                   *menuitem,
133                                                            EmpathyChatSpell              *chat_spell);
134 static EmpathyChatSpell *chat_spell_new                   (EmpathyChat                   *chat,
135                                                            const gchar                   *word,
136                                                            GtkTextIter                    start,
137                                                            GtkTextIter                    end);
138 static void             chat_spell_free                   (EmpathyChatSpell              *chat_spell);
139 static void             chat_composing_start              (EmpathyChat                   *chat);
140 static void             chat_composing_stop               (EmpathyChat                   *chat);
141 static void             chat_composing_remove_timeout     (EmpathyChat                   *chat);
142 static gboolean         chat_composing_stop_timeout_cb    (EmpathyChat                   *chat);
143 static void             chat_state_changed_cb             (EmpathyTpChat                 *tp_chat,
144                                                            EmpathyContact                *contact,
145                                                            TelepathyChannelChatState      state,
146                                                            EmpathyChat                   *chat);
147 static void             chat_add_logs                     (EmpathyChat                   *chat);
148 static gboolean         chat_scroll_down_idle_func        (EmpathyChat                   *chat);
149
150 enum {
151         COMPOSING,
152         NEW_MESSAGE,
153         NAME_CHANGED,
154         STATUS_CHANGED,
155         LAST_SIGNAL
156 };
157
158 static guint signals[LAST_SIGNAL] = { 0 };
159
160 G_DEFINE_TYPE (EmpathyChat, empathy_chat, G_TYPE_OBJECT);
161
162 static void
163 empathy_chat_class_init (EmpathyChatClass *klass)
164 {
165         GObjectClass *object_class;
166
167         object_class = G_OBJECT_CLASS (klass);
168
169         object_class->finalize = chat_finalize;
170
171         signals[COMPOSING] =
172                 g_signal_new ("composing",
173                               G_OBJECT_CLASS_TYPE (object_class),
174                               G_SIGNAL_RUN_LAST,
175                               0,
176                               NULL, NULL,
177                               g_cclosure_marshal_VOID__BOOLEAN,
178                               G_TYPE_NONE,
179                               1, G_TYPE_BOOLEAN);
180
181         signals[NEW_MESSAGE] =
182                 g_signal_new ("new-message",
183                               G_OBJECT_CLASS_TYPE (object_class),
184                               G_SIGNAL_RUN_LAST,
185                               0,
186                               NULL, NULL,
187                               empathy_marshal_VOID__OBJECT_BOOLEAN,
188                               G_TYPE_NONE,
189                               2, EMPATHY_TYPE_MESSAGE, G_TYPE_BOOLEAN);
190
191         signals[NAME_CHANGED] =
192                 g_signal_new ("name-changed",
193                               G_OBJECT_CLASS_TYPE (object_class),
194                               G_SIGNAL_RUN_LAST,
195                               0,
196                               NULL, NULL,
197                               g_cclosure_marshal_VOID__POINTER,
198                               G_TYPE_NONE,
199                               1, G_TYPE_POINTER);
200
201         signals[STATUS_CHANGED] =
202                 g_signal_new ("status-changed",
203                               G_OBJECT_CLASS_TYPE (object_class),
204                               G_SIGNAL_RUN_LAST,
205                               0,
206                               NULL, NULL,
207                               g_cclosure_marshal_VOID__VOID,
208                               G_TYPE_NONE,
209                               0);
210
211         g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
212 }
213
214 static void
215 empathy_chat_init (EmpathyChat *chat)
216 {
217         EmpathyChatPriv *priv;
218         GtkTextBuffer  *buffer;
219
220         chat->view = empathy_chat_view_new ();
221         chat->input_text_view = gtk_text_view_new ();
222
223         chat->is_first_char = TRUE;
224
225         g_object_set (chat->input_text_view,
226                       "pixels-above-lines", 2,
227                       "pixels-below-lines", 2,
228                       "pixels-inside-wrap", 1,
229                       "right-margin", 2,
230                       "left-margin", 2,
231                       "wrap-mode", GTK_WRAP_WORD_CHAR,
232                       NULL);
233
234         priv = GET_PRIV (chat);
235
236         priv->log_manager = empathy_log_manager_new ();
237         priv->default_window_height = -1;
238         priv->vscroll_visible = FALSE;
239         priv->sensitive = TRUE;
240         priv->sent_messages = NULL;
241         priv->sent_messages_index = -1;
242         priv->first_tp_chat = TRUE;
243
244         g_signal_connect (chat->input_text_view,
245                           "key_press_event",
246                           G_CALLBACK (chat_input_key_press_event_cb),
247                           chat);
248
249         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
250         g_signal_connect (buffer,
251                           "changed",
252                           G_CALLBACK (chat_input_text_buffer_changed_cb),
253                           chat);
254         g_signal_connect (chat->view,
255                           "focus_in_event",
256                           G_CALLBACK (chat_text_view_focus_in_event_cb),
257                           chat);
258
259         g_signal_connect (chat->input_text_view,
260                           "size_allocate",
261                           G_CALLBACK (chat_text_view_size_allocate_cb),
262                           chat);
263
264         g_signal_connect (chat->input_text_view,
265                           "realize",
266                           G_CALLBACK (chat_text_view_realize_cb),
267                           chat);
268
269         g_signal_connect (GTK_TEXT_VIEW (chat->input_text_view),
270                           "populate_popup",
271                           G_CALLBACK (chat_text_populate_popup_cb),
272                           chat);
273
274         /* create misspelt words identification tag */
275         gtk_text_buffer_create_tag (buffer,
276                                     "misspelled",
277                                     "underline", PANGO_UNDERLINE_ERROR,
278                                     NULL);
279 }
280
281 static void
282 chat_finalize (GObject *object)
283 {
284         EmpathyChat     *chat;
285         EmpathyChatPriv *priv;
286
287         chat = EMPATHY_CHAT (object);
288         priv = GET_PRIV (chat);
289
290         empathy_debug (DEBUG_DOMAIN, "Finalized: %p", object);
291
292         g_slist_foreach (priv->sent_messages, (GFunc) g_free, NULL);
293         g_slist_free (priv->sent_messages);
294
295         g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
296         g_list_free (priv->compositors);
297
298         chat_composing_remove_timeout (chat);
299         g_object_unref (chat->account);
300         g_object_unref (priv->log_manager);
301
302         if (priv->tp_chat) {
303                 g_object_unref (priv->tp_chat);
304         }
305
306         if (priv->scroll_idle_id) {
307                 g_source_remove (priv->scroll_idle_id);
308         }
309
310         g_free (priv->id);
311
312         G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
313 }
314
315 static void
316 chat_destroy_cb (EmpathyTpChat *tp_chat,
317                  EmpathyChat    *chat)
318 {
319         EmpathyChatPriv *priv;
320
321         priv = GET_PRIV (chat);
322
323         if (priv->tp_chat) {
324                 g_object_unref (priv->tp_chat);
325                 priv->tp_chat = NULL;
326         }
327         priv->sensitive = FALSE;
328
329         empathy_chat_view_append_event (chat->view, _("Disconnected"));
330         gtk_widget_set_sensitive (chat->input_text_view, FALSE);
331
332         if (EMPATHY_CHAT_GET_CLASS (chat)->set_tp_chat) {
333                 EMPATHY_CHAT_GET_CLASS (chat)->set_tp_chat (chat, NULL);
334         }
335 }
336
337 static void
338 chat_send (EmpathyChat  *chat,
339            const gchar *msg)
340 {
341         EmpathyChatPriv *priv;
342         EmpathyMessage  *message;
343
344         priv = GET_PRIV (chat);
345
346         if (G_STR_EMPTY (msg)) {
347                 return;
348         }
349
350         chat_sent_message_add (chat, msg);
351
352         if (g_str_has_prefix (msg, "/clear")) {
353                 empathy_chat_view_clear (chat->view);
354                 return;
355         }
356
357         /* FIXME: add here something to let group/privrate chat handle
358          *        some special messages */
359
360         message = empathy_message_new (msg);
361
362         empathy_tp_chat_send (priv->tp_chat, message);
363
364         g_object_unref (message);
365 }
366
367 static void
368 chat_input_text_view_send (EmpathyChat *chat)
369 {
370         EmpathyChatPriv *priv;
371         GtkTextBuffer  *buffer;
372         GtkTextIter     start, end;
373         gchar          *msg;
374
375         priv = GET_PRIV (chat);
376
377         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
378
379         gtk_text_buffer_get_bounds (buffer, &start, &end);
380         msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
381
382         /* clear the input field */
383         gtk_text_buffer_set_text (buffer, "", -1);
384
385         chat_send (chat, msg);
386
387         g_free (msg);
388
389         chat->is_first_char = TRUE;
390 }
391
392 static void
393 chat_message_received_cb (EmpathyTpChat  *tp_chat,
394                           EmpathyMessage *message,
395                           EmpathyChat    *chat)
396 {
397         EmpathyChatPriv *priv;
398         EmpathyContact  *sender;
399         time_t           timestamp;
400
401         priv = GET_PRIV (chat);
402
403         timestamp = empathy_message_get_timestamp (message);
404         if (timestamp <= priv->last_log_timestamp) {
405                 /* Do not take care of messages anterior of the last
406                  * logged message. Some Jabber chatroom sends messages
407                  * received before we joined the room, this avoid
408                  * displaying those messages if we already logged them
409                  * last time we joined that room. */
410                 empathy_debug (DEBUG_DOMAIN, "Skipping message because it is "
411                                "anterior of last logged message.");
412                 return;
413         }
414
415         sender = empathy_message_get_sender (message);
416         empathy_debug (DEBUG_DOMAIN, "Appending message ('%s')",
417                       empathy_contact_get_name (sender));
418
419         empathy_log_manager_add_message (priv->log_manager,
420                                          empathy_chat_get_id (chat),
421                                          empathy_chat_is_group_chat (chat),
422                                          message);
423
424         empathy_chat_view_append_message (chat->view, message);
425
426         if (empathy_chat_should_play_sound (chat)) {
427                 // FIXME: empathy_sound_play (EMPATHY_SOUND_CHAT);
428         }
429
430         /* We received a message so the contact is no more composing */
431         chat_state_changed_cb (tp_chat, sender,
432                                TP_CHANNEL_CHAT_STATE_ACTIVE,
433                                chat);
434
435         g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, FALSE);
436 }
437
438 static void
439 chat_send_error_cb (EmpathyTpChat                 *tp_chat,
440                     EmpathyMessage                *message,
441                     TelepathyChannelTextSendError  error_code,
442                     EmpathyChat                   *chat)
443 {
444         const gchar *error;
445         gchar       *str;
446
447         switch (error_code) {
448         case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
449                 error = _("offline");
450                 break;
451         case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
452                 error = _("invalid contact");
453                 break;
454         case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
455                 error = _("permission denied");
456                 break;
457         case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
458                 error = _("too long message");
459                 break;
460         case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
461                 error = _("not implemented");
462                 break;
463         default:
464                 error = _("unknown");
465                 break;
466         }
467
468         str = g_strdup_printf (_("Error sending message '%s': %s"),
469                                empathy_message_get_body (message),
470                                error);
471         empathy_chat_view_append_event (chat->view, str);
472         g_free (str);
473 }
474
475 void 
476 chat_sent_message_add (EmpathyChat  *chat,
477                        const gchar *str)
478 {
479         EmpathyChatPriv *priv;
480         GSList         *list;
481         GSList         *item;
482
483         priv = GET_PRIV (chat);
484
485         /* Save the sent message in our repeat buffer */
486         list = priv->sent_messages;
487         
488         /* Remove any other occurances of this msg */
489         while ((item = g_slist_find_custom (list, str, (GCompareFunc) strcmp)) != NULL) {
490                 list = g_slist_remove_link (list, item);
491                 g_free (item->data);
492                 g_slist_free1 (item);
493         }
494
495         /* Trim the list to the last 10 items */
496         while (g_slist_length (list) > 10) {
497                 item = g_slist_last (list);
498                 if (item) {
499                         list = g_slist_remove_link (list, item);
500                         g_free (item->data);
501                         g_slist_free1 (item);
502                 }
503         }
504
505         /* Add new message */
506         list = g_slist_prepend (list, g_strdup (str));
507
508         /* Set list and reset the index */
509         priv->sent_messages = list;
510         priv->sent_messages_index = -1;
511 }
512
513 const gchar *
514 chat_sent_message_get_next (EmpathyChat *chat)
515 {
516         EmpathyChatPriv *priv;
517         gint            max;
518         
519         priv = GET_PRIV (chat);
520
521         if (!priv->sent_messages) {
522                 empathy_debug (DEBUG_DOMAIN, 
523                               "No sent messages, next message is NULL");
524                 return NULL;
525         }
526
527         max = g_slist_length (priv->sent_messages) - 1;
528
529         if (priv->sent_messages_index < max) {
530                 priv->sent_messages_index++;
531         }
532         
533         empathy_debug (DEBUG_DOMAIN, 
534                       "Returning next message index:%d",
535                       priv->sent_messages_index);
536
537         return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
538 }
539
540 const gchar *
541 chat_sent_message_get_last (EmpathyChat *chat)
542 {
543         EmpathyChatPriv *priv;
544
545         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
546
547         priv = GET_PRIV (chat);
548         
549         if (!priv->sent_messages) {
550                 empathy_debug (DEBUG_DOMAIN, 
551                               "No sent messages, last message is NULL");
552                 return NULL;
553         }
554
555         if (priv->sent_messages_index >= 0) {
556                 priv->sent_messages_index--;
557         }
558
559         empathy_debug (DEBUG_DOMAIN, 
560                       "Returning last message index:%d",
561                       priv->sent_messages_index);
562
563         return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
564 }
565
566 static gboolean
567 chat_input_key_press_event_cb (GtkWidget   *widget,
568                                GdkEventKey *event,
569                                EmpathyChat  *chat)
570 {
571         EmpathyChatPriv *priv;
572         GtkAdjustment  *adj;
573         gdouble         val;
574         GtkWidget      *text_view_sw;
575
576         priv = GET_PRIV (chat);
577
578         if (event->keyval == GDK_Tab && !(event->state & GDK_CONTROL_MASK)) {
579                 return TRUE;
580         }
581
582         /* Catch ctrl+up/down so we can traverse messages we sent */
583         if ((event->state & GDK_CONTROL_MASK) && 
584             (event->keyval == GDK_Up || 
585              event->keyval == GDK_Down)) {
586                 GtkTextBuffer *buffer;
587                 const gchar   *str;
588
589                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
590
591                 if (event->keyval == GDK_Up) {
592                         str = chat_sent_message_get_next (chat);
593                 } else {
594                         str = chat_sent_message_get_last (chat);
595                 }
596
597                 g_signal_handlers_block_by_func (buffer, 
598                                                  chat_input_text_buffer_changed_cb,
599                                                  chat);
600                 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
601                 g_signal_handlers_unblock_by_func (buffer, 
602                                                    chat_input_text_buffer_changed_cb,
603                                                    chat);
604
605                 return TRUE;    
606         }
607
608         /* Catch enter but not ctrl/shift-enter */
609         if (IS_ENTER (event->keyval) && !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
610                 GtkTextView *view;
611
612                 /* This is to make sure that kinput2 gets the enter. And if
613                  * it's handled there we shouldn't send on it. This is because
614                  * kinput2 uses Enter to commit letters. See:
615                  * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
616                  */
617
618                 view = GTK_TEXT_VIEW (chat->input_text_view);
619                 if (gtk_im_context_filter_keypress (view->im_context, event)) {
620                         GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
621                         return TRUE;
622                 }
623
624                 chat_input_text_view_send (chat);
625                 return TRUE;
626         }
627
628         text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
629
630         if (IS_ENTER (event->keyval) && (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
631                 /* Newline for shift-enter. */
632                 return FALSE;
633         }
634         else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
635                  event->keyval == GDK_Page_Up) {
636                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
637                 gtk_adjustment_set_value (adj, adj->value - adj->page_size);
638
639                 return TRUE;
640         }
641         else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
642                  event->keyval == GDK_Page_Down) {
643                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
644                 val = MIN (adj->value + adj->page_size, adj->upper - adj->page_size);
645                 gtk_adjustment_set_value (adj, val);
646
647                 return TRUE;
648         }
649
650         return FALSE;
651 }
652
653 static gboolean
654 chat_text_view_focus_in_event_cb (GtkWidget  *widget,
655                                   GdkEvent   *event,
656                                   EmpathyChat *chat)
657 {
658         gtk_widget_grab_focus (chat->input_text_view);
659
660         return TRUE;
661 }
662
663 static void
664 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
665                                    EmpathyChat    *chat)
666 {
667         EmpathyChatPriv *priv;
668         GtkTextIter     start, end;
669         gchar          *str;
670         gboolean        spell_checker = FALSE;
671
672         priv = GET_PRIV (chat);
673
674         if (gtk_text_buffer_get_char_count (buffer) == 0) {
675                 chat_composing_stop (chat);
676         } else {
677                 chat_composing_start (chat);
678         }
679
680         empathy_conf_get_bool (empathy_conf_get (),
681                               EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
682                               &spell_checker);
683
684         if (chat->is_first_char) {
685                 GtkRequisition  req;
686                 gint            window_height;
687                 GtkWidget      *dialog;
688                 GtkAllocation  *allocation;
689
690                 /* Save the window's size */
691                 dialog = empathy_chat_window_get_dialog (priv->window);
692                 gtk_window_get_size (GTK_WINDOW (dialog),
693                                      NULL, &window_height);
694
695                 gtk_widget_size_request (chat->input_text_view, &req);
696
697                 allocation = &GTK_WIDGET (chat->view)->allocation;
698
699                 priv->default_window_height = window_height;
700                 priv->last_input_height = req.height;
701                 priv->padding_height = window_height - req.height - allocation->height;
702
703                 chat->is_first_char = FALSE;
704         }
705
706         gtk_text_buffer_get_start_iter (buffer, &start);
707
708         if (!spell_checker) {
709                 gtk_text_buffer_get_end_iter (buffer, &end);
710                 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
711                 return;
712         }
713
714         if (!empathy_spell_supported ()) {
715                 return;
716         }
717
718         /* NOTE: this is really inefficient, we shouldn't have to
719            reiterate the whole buffer each time and check each work
720            every time. */
721         while (TRUE) {
722                 gboolean correct = FALSE;
723
724                 /* if at start */
725                 if (gtk_text_iter_is_start (&start)) {
726                         end = start;
727
728                         if (!gtk_text_iter_forward_word_end (&end)) {
729                                 /* no whole word yet */
730                                 break;
731                         }
732                 } else {
733                         if (!gtk_text_iter_forward_word_end (&end)) {
734                                 /* must be the end of the buffer */
735                                 break;
736                         }
737
738                         start = end;
739                         gtk_text_iter_backward_word_start (&start);
740                 }
741
742                 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
743
744                 /* spell check string */
745                 if (!empathy_chat_get_is_command (str)) {
746                         correct = empathy_spell_check (str);
747                 } else {
748                         correct = TRUE;
749                 }
750
751                 if (!correct) {
752                         gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
753                 } else {
754                         gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
755                 }
756
757                 g_free (str);
758
759                 /* set start iter to the end iters position */
760                 start = end;
761         }
762 }
763
764 typedef struct {
765         GtkWidget *window;
766         gint       width;
767         gint       height;
768 } ChangeSizeData;
769
770 static gboolean
771 chat_change_size_in_idle_cb (ChangeSizeData *data)
772 {
773         gtk_window_resize (GTK_WINDOW (data->window),
774                            data->width, data->height);
775
776         return FALSE;
777 }
778
779 static void
780 chat_text_view_scroll_hide_cb (GtkWidget  *widget,
781                                EmpathyChat *chat)
782 {
783         EmpathyChatPriv *priv;
784         GtkWidget      *sw;
785
786         priv = GET_PRIV (chat);
787
788         priv->vscroll_visible = FALSE;
789         g_signal_handlers_disconnect_by_func (widget, chat_text_view_scroll_hide_cb, chat);
790
791         sw = gtk_widget_get_parent (chat->input_text_view);
792         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
793                                         GTK_POLICY_NEVER,
794                                         GTK_POLICY_NEVER);
795         g_object_set (sw, "height-request", -1, NULL);
796 }
797
798 static void
799 chat_text_view_size_allocate_cb (GtkWidget     *widget,
800                                  GtkAllocation *allocation,
801                                  EmpathyChat    *chat)
802 {
803         EmpathyChatPriv *priv;
804         gint            width;
805         GtkWidget      *dialog;
806         ChangeSizeData *data;
807         gint            window_height;
808         gint            new_height;
809         GtkAllocation  *view_allocation;
810         gint            current_height;
811         gint            diff;
812         GtkWidget      *sw;
813
814         priv = GET_PRIV (chat);
815
816         if (priv->default_window_height <= 0) {
817                 return;
818         }
819
820         sw = gtk_widget_get_parent (widget);
821         if (sw->allocation.height >= MAX_INPUT_HEIGHT && !priv->vscroll_visible) {
822                 GtkWidget *vscroll;
823
824                 priv->vscroll_visible = TRUE;
825                 gtk_widget_set_size_request (sw, sw->allocation.width, MAX_INPUT_HEIGHT);
826                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
827                                                 GTK_POLICY_NEVER,
828                                                 GTK_POLICY_AUTOMATIC);
829                 vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (sw));
830                 g_signal_connect (vscroll, "hide",
831                                   G_CALLBACK (chat_text_view_scroll_hide_cb),
832                                   chat);
833         }
834
835         if (priv->last_input_height <= allocation->height) {
836                 priv->last_input_height = allocation->height;
837                 return;
838         }
839
840         diff = priv->last_input_height - allocation->height;
841         priv->last_input_height = allocation->height;
842
843         view_allocation = &GTK_WIDGET (chat->view)->allocation;
844
845         dialog = empathy_chat_window_get_dialog (priv->window);
846         gtk_window_get_size (GTK_WINDOW (dialog), NULL, &current_height);
847
848         new_height = view_allocation->height + priv->padding_height + allocation->height - diff;
849
850         if (new_height <= priv->default_window_height) {
851                 window_height = priv->default_window_height;
852         } else {
853                 window_height = new_height;
854         }
855
856         if (current_height <= window_height) {
857                 return;
858         }
859
860         /* Restore the window's size */
861         gtk_window_get_size (GTK_WINDOW (dialog), &width, NULL);
862
863         data = g_new0 (ChangeSizeData, 1);
864         data->window = dialog;
865         data->width  = width;
866         data->height = window_height;
867
868         g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
869                          (GSourceFunc) chat_change_size_in_idle_cb,
870                          data, g_free);
871 }
872
873 static void
874 chat_text_view_realize_cb (GtkWidget  *widget,
875                            EmpathyChat *chat)
876 {
877         empathy_debug (DEBUG_DOMAIN, "Setting focus to the input text view");
878         gtk_widget_grab_focus (widget);
879 }
880
881 static void
882 chat_insert_smiley_activate_cb (GtkWidget   *menuitem,
883                                 EmpathyChat *chat)
884 {
885         GtkTextBuffer *buffer;
886         GtkTextIter    iter;
887         const gchar   *smiley;
888
889         smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
890
891         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
892
893         gtk_text_buffer_get_end_iter (buffer, &iter);
894         gtk_text_buffer_insert (buffer, &iter, smiley, -1);
895
896         gtk_text_buffer_get_end_iter (buffer, &iter);
897         gtk_text_buffer_insert (buffer, &iter, " ", -1);
898 }
899
900 static void
901 chat_text_populate_popup_cb (GtkTextView *view,
902                              GtkMenu     *menu,
903                              EmpathyChat  *chat)
904 {
905         EmpathyChatPriv  *priv;
906         GtkTextBuffer   *buffer;
907         GtkTextTagTable *table;
908         GtkTextTag      *tag;
909         gint             x, y;
910         GtkTextIter      iter, start, end;
911         GtkWidget       *item;
912         gchar           *str = NULL;
913         EmpathyChatSpell *chat_spell;
914         GtkWidget       *smiley_menu;
915
916         priv = GET_PRIV (chat);
917
918         /* Add the emoticon menu. */
919         item = gtk_separator_menu_item_new ();
920         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
921         gtk_widget_show (item);
922
923         item = gtk_menu_item_new_with_mnemonic (_("Insert Smiley"));
924         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
925         gtk_widget_show (item);
926
927         smiley_menu = empathy_chat_view_get_smiley_menu (
928                 G_CALLBACK (chat_insert_smiley_activate_cb),
929                 chat);
930         gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
931
932         /* Add the spell check menu item. */
933         buffer = gtk_text_view_get_buffer (view);
934         table = gtk_text_buffer_get_tag_table (buffer);
935
936         tag = gtk_text_tag_table_lookup (table, "misspelled");
937
938         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
939
940         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
941                                                GTK_TEXT_WINDOW_WIDGET,
942                                                x, y,
943                                                &x, &y);
944
945         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
946
947         start = end = iter;
948
949         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
950             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
951
952                 str = gtk_text_buffer_get_text (buffer,
953                                                 &start, &end, FALSE);
954         }
955
956         if (G_STR_EMPTY (str)) {
957                 return;
958         }
959
960         chat_spell = chat_spell_new (chat, str, start, end);
961
962         g_object_set_data_full (G_OBJECT (menu),
963                                 "chat_spell", chat_spell,
964                                 (GDestroyNotify) chat_spell_free);
965
966         item = gtk_separator_menu_item_new ();
967         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
968         gtk_widget_show (item);
969
970         item = gtk_menu_item_new_with_mnemonic (_("_Check Word Spelling..."));
971         g_signal_connect (item,
972                           "activate",
973                           G_CALLBACK (chat_text_check_word_spelling_cb),
974                           chat_spell);
975         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
976         gtk_widget_show (item);
977 }
978
979 static void
980 chat_text_check_word_spelling_cb (GtkMenuItem     *menuitem,
981                                   EmpathyChatSpell *chat_spell)
982 {
983         empathy_spell_dialog_show (chat_spell->chat,
984                                   chat_spell->start,
985                                   chat_spell->end,
986                                   chat_spell->word);
987 }
988
989 static EmpathyChatSpell *
990 chat_spell_new (EmpathyChat  *chat,
991                 const gchar *word,
992                 GtkTextIter  start,
993                 GtkTextIter  end)
994 {
995         EmpathyChatSpell *chat_spell;
996
997         chat_spell = g_new0 (EmpathyChatSpell, 1);
998
999         chat_spell->chat = g_object_ref (chat);
1000         chat_spell->word = g_strdup (word);
1001         chat_spell->start = start;
1002         chat_spell->end = end;
1003
1004         return chat_spell;
1005 }
1006
1007 static void
1008 chat_spell_free (EmpathyChatSpell *chat_spell)
1009 {
1010         g_object_unref (chat_spell->chat);
1011         g_free (chat_spell->word);
1012         g_free (chat_spell);
1013 }
1014
1015 static void
1016 chat_composing_start (EmpathyChat *chat)
1017 {
1018         EmpathyChatPriv *priv;
1019
1020         priv = GET_PRIV (chat);
1021
1022         if (priv->composing_stop_timeout_id) {
1023                 /* Just restart the timeout */
1024                 chat_composing_remove_timeout (chat);
1025         } else {
1026                 empathy_tp_chat_set_state (priv->tp_chat,
1027                                            TP_CHANNEL_CHAT_STATE_COMPOSING);
1028         }
1029
1030         priv->composing_stop_timeout_id = g_timeout_add_seconds (
1031                 COMPOSING_STOP_TIMEOUT,
1032                 (GSourceFunc) chat_composing_stop_timeout_cb,
1033                 chat);
1034 }
1035
1036 static void
1037 chat_composing_stop (EmpathyChat *chat)
1038 {
1039         EmpathyChatPriv *priv;
1040
1041         priv = GET_PRIV (chat);
1042
1043         chat_composing_remove_timeout (chat);
1044         empathy_tp_chat_set_state (priv->tp_chat,
1045                                    TP_CHANNEL_CHAT_STATE_ACTIVE);
1046 }
1047
1048 static void
1049 chat_composing_remove_timeout (EmpathyChat *chat)
1050 {
1051         EmpathyChatPriv *priv;
1052
1053         priv = GET_PRIV (chat);
1054
1055         if (priv->composing_stop_timeout_id) {
1056                 g_source_remove (priv->composing_stop_timeout_id);
1057                 priv->composing_stop_timeout_id = 0;
1058         }
1059 }
1060
1061 static gboolean
1062 chat_composing_stop_timeout_cb (EmpathyChat *chat)
1063 {
1064         EmpathyChatPriv *priv;
1065
1066         priv = GET_PRIV (chat);
1067
1068         priv->composing_stop_timeout_id = 0;
1069         empathy_tp_chat_set_state (priv->tp_chat,
1070                                    TP_CHANNEL_CHAT_STATE_PAUSED);
1071
1072         return FALSE;
1073 }
1074
1075 static void
1076 chat_state_changed_cb (EmpathyTpChat             *tp_chat,
1077                        EmpathyContact             *contact,
1078                        TelepathyChannelChatState  state,
1079                        EmpathyChat                *chat)
1080 {
1081         EmpathyChatPriv *priv;
1082         GList          *l;
1083         gboolean        was_composing;
1084
1085         priv = GET_PRIV (chat);
1086
1087         if (empathy_contact_is_user (contact)) {
1088                 /* We don't care about our own chat state */
1089                 return;
1090         }
1091
1092         was_composing = (priv->compositors != NULL);
1093
1094         /* Find the contact in the list. After that l is the list elem or NULL */
1095         for (l = priv->compositors; l; l = l->next) {
1096                 if (empathy_contact_equal (contact, l->data)) {
1097                         break;
1098                 }
1099         }
1100
1101         switch (state) {
1102         case TP_CHANNEL_CHAT_STATE_GONE:
1103         case TP_CHANNEL_CHAT_STATE_INACTIVE:
1104         case TP_CHANNEL_CHAT_STATE_PAUSED:
1105         case TP_CHANNEL_CHAT_STATE_ACTIVE:
1106                 /* Contact is not composing */
1107                 if (l) {
1108                         priv->compositors = g_list_remove_link (priv->compositors, l);
1109                         g_object_unref (l->data);
1110                         g_list_free1 (l);
1111                 }
1112                 break;
1113         case TP_CHANNEL_CHAT_STATE_COMPOSING:
1114                 /* Contact is composing */
1115                 if (!l) {
1116                         priv->compositors = g_list_prepend (priv->compositors,
1117                                                             g_object_ref (contact));
1118                 }
1119                 break;
1120         default:
1121                 g_assert_not_reached ();
1122         }
1123
1124         empathy_debug (DEBUG_DOMAIN, "Was composing: %s now composing: %s",
1125                       was_composing ? "yes" : "no",
1126                       priv->compositors ? "yes" : "no");
1127
1128         if ((was_composing && !priv->compositors) ||
1129             (!was_composing && priv->compositors)) {
1130                 /* Composing state changed */
1131                 g_signal_emit (chat, signals[COMPOSING], 0,
1132                                priv->compositors != NULL);
1133         }
1134 }
1135
1136 static void
1137 chat_add_logs (EmpathyChat *chat)
1138 {
1139         EmpathyChatPriv *priv;
1140         GList          *messages, *l;
1141         guint           num_messages;
1142         guint           i;
1143
1144         priv = GET_PRIV (chat);
1145
1146         /* Turn off scrolling temporarily */
1147         empathy_chat_view_scroll (chat->view, FALSE);
1148
1149         /* Add messages from last conversation */
1150         messages = empathy_log_manager_get_last_messages (priv->log_manager,
1151                                                           chat->account,
1152                                                           empathy_chat_get_id (chat),
1153                                                           empathy_chat_is_group_chat (chat));
1154         num_messages  = g_list_length (messages);
1155
1156         for (l = messages, i = 0; l; l = l->next, i++) {
1157                 EmpathyMessage *message;
1158
1159                 message = l->data;
1160
1161                 /* Only add 10 last messages */
1162                 if (num_messages - i > 10) {
1163                         g_object_unref (message);
1164                         continue;
1165                 }
1166
1167                 priv->last_log_timestamp = empathy_message_get_timestamp (message);
1168                 empathy_chat_view_append_message (chat->view, message);
1169
1170                 g_object_unref (message);
1171         }
1172         g_list_free (messages);
1173
1174         /* Turn back on scrolling */
1175         empathy_chat_view_scroll (chat->view, TRUE);
1176
1177         /* Scroll to the most recent messages, we reference the chat
1178          * for the duration of the scroll func.
1179          */
1180         priv->scroll_idle_id = g_idle_add ((GSourceFunc) chat_scroll_down_idle_func, 
1181                                            g_object_ref (chat));
1182 }
1183
1184 /* Scroll down after the back-log has been received. */
1185 static gboolean
1186 chat_scroll_down_idle_func (EmpathyChat *chat)
1187 {
1188         EmpathyChatPriv *priv;
1189
1190         priv = GET_PRIV (chat);
1191
1192         empathy_chat_scroll_down (chat);
1193         g_object_unref (chat);
1194
1195         priv->scroll_idle_id = 0;
1196
1197         return FALSE;
1198 }
1199
1200 gboolean
1201 empathy_chat_get_is_command (const gchar *str)
1202 {
1203         g_return_val_if_fail (str != NULL, FALSE);
1204
1205         if (str[0] != '/') {
1206                 return FALSE;
1207         }
1208
1209         if (g_str_has_prefix (str, "/me")) {
1210                 return TRUE;
1211         }
1212         else if (g_str_has_prefix (str, "/nick")) {
1213                 return TRUE;
1214         }
1215         else if (g_str_has_prefix (str, "/topic")) {
1216                 return TRUE;
1217         }
1218
1219         return FALSE;
1220 }
1221
1222 void
1223 empathy_chat_correct_word (EmpathyChat  *chat,
1224                           GtkTextIter  start,
1225                           GtkTextIter  end,
1226                           const gchar *new_word)
1227 {
1228         GtkTextBuffer *buffer;
1229
1230         g_return_if_fail (chat != NULL);
1231         g_return_if_fail (new_word != NULL);
1232
1233         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1234
1235         gtk_text_buffer_delete (buffer, &start, &end);
1236         gtk_text_buffer_insert (buffer, &start,
1237                                 new_word,
1238                                 -1);
1239 }
1240
1241 const gchar *
1242 empathy_chat_get_name (EmpathyChat *chat)
1243 {
1244         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1245
1246         if (EMPATHY_CHAT_GET_CLASS (chat)->get_name) {
1247                 return EMPATHY_CHAT_GET_CLASS (chat)->get_name (chat);
1248         }
1249
1250         return NULL;
1251 }
1252
1253 gchar *
1254 empathy_chat_get_tooltip (EmpathyChat *chat)
1255 {
1256         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1257
1258         if (EMPATHY_CHAT_GET_CLASS (chat)->get_tooltip) {
1259                 return EMPATHY_CHAT_GET_CLASS (chat)->get_tooltip (chat);
1260         }
1261
1262         return NULL;
1263 }
1264
1265 const gchar *
1266 empathy_chat_get_status_icon_name (EmpathyChat *chat)
1267 {
1268         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1269
1270         if (EMPATHY_CHAT_GET_CLASS (chat)->get_status_icon_name) {
1271                 return EMPATHY_CHAT_GET_CLASS (chat)->get_status_icon_name (chat);
1272         }
1273
1274         return NULL;
1275 }
1276
1277 GtkWidget *
1278 empathy_chat_get_widget (EmpathyChat *chat)
1279 {
1280         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1281
1282         if (EMPATHY_CHAT_GET_CLASS (chat)->get_widget) {
1283                 return EMPATHY_CHAT_GET_CLASS (chat)->get_widget (chat);
1284         }
1285
1286         return NULL;
1287 }
1288
1289 gboolean
1290 empathy_chat_is_group_chat (EmpathyChat *chat)
1291 {
1292         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
1293
1294         if (EMPATHY_CHAT_GET_CLASS (chat)->is_group_chat) {
1295                 return EMPATHY_CHAT_GET_CLASS (chat)->is_group_chat (chat);
1296         }
1297
1298         return FALSE;
1299 }
1300
1301 gboolean 
1302 empathy_chat_is_connected (EmpathyChat *chat)
1303 {
1304         EmpathyChatPriv *priv;
1305
1306         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
1307
1308         priv = GET_PRIV (chat);
1309
1310         return (priv->tp_chat != NULL);
1311 }
1312
1313 void
1314 empathy_chat_save_geometry (EmpathyChat *chat,
1315                            gint        x,
1316                            gint        y,
1317                            gint        w,
1318                            gint        h)
1319 {
1320         empathy_geometry_save (empathy_chat_get_id (chat), x, y, w, h);
1321 }
1322
1323 void
1324 empathy_chat_load_geometry (EmpathyChat *chat,
1325                            gint       *x,
1326                            gint       *y,
1327                            gint       *w,
1328                            gint       *h)
1329 {
1330         empathy_geometry_load (empathy_chat_get_id (chat), x, y, w, h);
1331 }
1332
1333 void
1334 empathy_chat_set_tp_chat (EmpathyChat   *chat,
1335                           EmpathyTpChat *tp_chat)
1336 {
1337         EmpathyChatPriv *priv;
1338         GList           *messages, *l;
1339
1340         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1341         g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
1342
1343         priv = GET_PRIV (chat);
1344
1345         if (tp_chat == priv->tp_chat) {
1346                 return;
1347         }
1348
1349         if (priv->tp_chat) {
1350                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1351                                                       chat_message_received_cb,
1352                                                       chat);
1353                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1354                                                       chat_send_error_cb,
1355                                                       chat);
1356                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1357                                                       chat_destroy_cb,
1358                                                       chat);
1359                 g_object_unref (priv->tp_chat);
1360         }
1361
1362         g_free (priv->id);
1363         priv->tp_chat = g_object_ref (tp_chat);
1364         priv->id = g_strdup (empathy_tp_chat_get_id (tp_chat));
1365
1366         if (priv->first_tp_chat) {
1367                 chat_add_logs (chat);
1368                 priv->first_tp_chat = FALSE;
1369         }
1370
1371         g_signal_connect (tp_chat, "message-received",
1372                           G_CALLBACK (chat_message_received_cb),
1373                           chat);
1374         g_signal_connect (tp_chat, "send-error",
1375                           G_CALLBACK (chat_send_error_cb),
1376                           chat);
1377         g_signal_connect (tp_chat, "chat-state-changed",
1378                           G_CALLBACK (chat_state_changed_cb),
1379                           chat);
1380         g_signal_connect (tp_chat, "destroy",
1381                           G_CALLBACK (chat_destroy_cb),
1382                           chat);
1383
1384         /* Get pending messages */
1385         empathy_tp_chat_set_acknowledge (tp_chat, TRUE);
1386         messages = empathy_tp_chat_get_pendings (tp_chat);
1387         for (l = messages; l; l = l->next) {
1388                 chat_message_received_cb (tp_chat, l->data, chat);
1389                 g_object_unref (l->data);
1390         }
1391         g_list_free (messages);
1392
1393         if (!priv->sensitive) {
1394                 gtk_widget_set_sensitive (chat->input_text_view, TRUE);
1395                 empathy_chat_view_append_event (chat->view, _("Connected"));
1396                 priv->sensitive = TRUE;
1397         }
1398
1399         if (EMPATHY_CHAT_GET_CLASS (chat)->set_tp_chat) {
1400                 EMPATHY_CHAT_GET_CLASS (chat)->set_tp_chat (chat, tp_chat);
1401         }
1402
1403 }
1404
1405 const gchar *
1406 empathy_chat_get_id (EmpathyChat *chat)
1407 {
1408         EmpathyChatPriv *priv;
1409
1410         priv = GET_PRIV (chat);
1411
1412         return priv->id;
1413 }
1414
1415 void
1416 empathy_chat_clear (EmpathyChat *chat)
1417 {
1418         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1419
1420         empathy_chat_view_clear (chat->view);
1421 }
1422
1423 void
1424 empathy_chat_set_window (EmpathyChat       *chat,
1425                         EmpathyChatWindow *window)
1426 {
1427         EmpathyChatPriv *priv;
1428
1429         priv = GET_PRIV (chat);
1430         priv->window = window;
1431 }
1432
1433 EmpathyChatWindow *
1434 empathy_chat_get_window (EmpathyChat *chat)
1435 {
1436         EmpathyChatPriv *priv;
1437
1438         priv = GET_PRIV (chat);
1439
1440         return priv->window;
1441 }
1442
1443 void
1444 empathy_chat_scroll_down (EmpathyChat *chat)
1445 {
1446         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1447
1448         empathy_chat_view_scroll_down (chat->view);
1449 }
1450
1451 void
1452 empathy_chat_cut (EmpathyChat *chat)
1453 {
1454         GtkTextBuffer *buffer;
1455
1456         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1457
1458         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1459         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1460                 GtkClipboard *clipboard;
1461
1462                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1463
1464                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
1465         }
1466 }
1467
1468 void
1469 empathy_chat_copy (EmpathyChat *chat)
1470 {
1471         GtkTextBuffer *buffer;
1472
1473         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1474
1475         if (empathy_chat_view_get_selection_bounds (chat->view, NULL, NULL)) {
1476                 empathy_chat_view_copy_clipboard (chat->view);
1477                 return;
1478         }
1479
1480         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1481         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1482                 GtkClipboard *clipboard;
1483
1484                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1485
1486                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
1487         }
1488 }
1489
1490 void
1491 empathy_chat_paste (EmpathyChat *chat)
1492 {
1493         GtkTextBuffer *buffer;
1494         GtkClipboard  *clipboard;
1495
1496         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1497
1498         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1499         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1500
1501         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
1502 }
1503
1504 void
1505 empathy_chat_present (EmpathyChat *chat)
1506 {
1507         EmpathyChatPriv *priv;
1508
1509         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1510
1511         priv = GET_PRIV (chat);
1512
1513         if (priv->window == NULL) {
1514                 EmpathyChatWindow *window;
1515
1516                 window = empathy_chat_window_get_default ();
1517                 if (!window) {
1518                         window = empathy_chat_window_new ();
1519                 }
1520
1521                 empathy_chat_window_add_chat (window, chat);
1522         }
1523
1524         empathy_chat_window_switch_to_chat (priv->window, chat);
1525         empathy_window_present (
1526                 GTK_WINDOW (empathy_chat_window_get_dialog (priv->window)),
1527                 TRUE);
1528
1529         gtk_widget_grab_focus (chat->input_text_view); 
1530 }
1531
1532 gboolean
1533 empathy_chat_should_play_sound (EmpathyChat *chat)
1534 {
1535         EmpathyChatWindow *window;
1536         gboolean          play = TRUE;
1537
1538         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
1539
1540         window = empathy_chat_get_window (chat);
1541         if (!window) {
1542                 return TRUE;
1543         }
1544
1545         play = !empathy_chat_window_has_focus (window);
1546
1547         return play;
1548 }
1549
1550 gboolean
1551 empathy_chat_should_highlight_nick (EmpathyMessage *message)
1552 {
1553         EmpathyContact *contact;
1554         const gchar   *msg, *to;
1555         gchar         *cf_msg, *cf_to;
1556         gchar         *ch;
1557         gboolean       ret_val;
1558
1559         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
1560
1561         empathy_debug (DEBUG_DOMAIN, "Highlighting nickname");
1562
1563         ret_val = FALSE;
1564
1565         msg = empathy_message_get_body (message);
1566         if (!msg) {
1567                 return FALSE;
1568         }
1569
1570         contact = empathy_message_get_receiver (message);
1571         if (!contact || !empathy_contact_is_user (contact)) {
1572                 return FALSE;
1573         }
1574
1575         to = empathy_contact_get_name (contact);
1576         if (!to) {
1577                 return FALSE;
1578         }
1579
1580         cf_msg = g_utf8_casefold (msg, -1);
1581         cf_to = g_utf8_casefold (to, -1);
1582
1583         ch = strstr (cf_msg, cf_to);
1584         if (ch == NULL) {
1585                 goto finished;
1586         }
1587
1588         if (ch != cf_msg) {
1589                 /* Not first in the message */
1590                 if ((*(ch - 1) != ' ') &&
1591                     (*(ch - 1) != ',') &&
1592                     (*(ch - 1) != '.')) {
1593                         goto finished;
1594                 }
1595         }
1596
1597         ch = ch + strlen (cf_to);
1598         if (ch >= cf_msg + strlen (cf_msg)) {
1599                 ret_val = TRUE;
1600                 goto finished;
1601         }
1602
1603         if ((*ch == ' ') ||
1604             (*ch == ',') ||
1605             (*ch == '.') ||
1606             (*ch == ':')) {
1607                 ret_val = TRUE;
1608                 goto finished;
1609         }
1610
1611 finished:
1612         g_free (cf_msg);
1613         g_free (cf_to);
1614
1615         return ret_val;
1616 }
1617