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