]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat.c
Conflicts:
[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                                                            TpChannelTextSendError  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                                                            TpChannelChatState      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                     TpChannelTextSendError  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         /* Catch ctrl+up/down so we can traverse messages we sent */
579         if ((event->state & GDK_CONTROL_MASK) && 
580             (event->keyval == GDK_Up || 
581              event->keyval == GDK_Down)) {
582                 GtkTextBuffer *buffer;
583                 const gchar   *str;
584
585                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
586
587                 if (event->keyval == GDK_Up) {
588                         str = chat_sent_message_get_next (chat);
589                 } else {
590                         str = chat_sent_message_get_last (chat);
591                 }
592
593                 g_signal_handlers_block_by_func (buffer, 
594                                                  chat_input_text_buffer_changed_cb,
595                                                  chat);
596                 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
597                 g_signal_handlers_unblock_by_func (buffer, 
598                                                    chat_input_text_buffer_changed_cb,
599                                                    chat);
600
601                 return TRUE;    
602         }
603
604         /* Catch enter but not ctrl/shift-enter */
605         if (IS_ENTER (event->keyval) &&
606             !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
607                 GtkTextView *view;
608
609                 /* This is to make sure that kinput2 gets the enter. And if
610                  * it's handled there we shouldn't send on it. This is because
611                  * kinput2 uses Enter to commit letters. See:
612                  * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
613                  */
614
615                 view = GTK_TEXT_VIEW (chat->input_text_view);
616                 if (gtk_im_context_filter_keypress (view->im_context, event)) {
617                         GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
618                         return TRUE;
619                 }
620
621                 chat_input_text_view_send (chat);
622                 return TRUE;
623         }
624
625         text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
626
627         if (IS_ENTER (event->keyval) &&
628             (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
629                 /* Newline for shift/control-enter. */
630                 return FALSE;
631         }
632         else if (!(event->state & GDK_CONTROL_MASK) &&
633                  event->keyval == GDK_Page_Up) {
634                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
635                 gtk_adjustment_set_value (adj, adj->value - adj->page_size);
636
637                 return TRUE;
638         }
639         else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
640                  event->keyval == GDK_Page_Down) {
641                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
642                 val = MIN (adj->value + adj->page_size, adj->upper - adj->page_size);
643                 gtk_adjustment_set_value (adj, val);
644
645                 return TRUE;
646         }
647
648         if (EMPATHY_CHAT_GET_CLASS (chat)->key_press_event) {
649                 return EMPATHY_CHAT_GET_CLASS (chat)->key_press_event (chat, event);
650         }
651
652         return FALSE;
653 }
654
655 static gboolean
656 chat_text_view_focus_in_event_cb (GtkWidget  *widget,
657                                   GdkEvent   *event,
658                                   EmpathyChat *chat)
659 {
660         gtk_widget_grab_focus (chat->input_text_view);
661
662         return TRUE;
663 }
664
665 static void
666 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
667                                    EmpathyChat    *chat)
668 {
669         EmpathyChatPriv *priv;
670         GtkTextIter     start, end;
671         gchar          *str;
672         gboolean        spell_checker = FALSE;
673
674         priv = GET_PRIV (chat);
675
676         if (gtk_text_buffer_get_char_count (buffer) == 0) {
677                 chat_composing_stop (chat);
678         } else {
679                 chat_composing_start (chat);
680         }
681
682         empathy_conf_get_bool (empathy_conf_get (),
683                               EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
684                               &spell_checker);
685
686         if (chat->is_first_char) {
687                 GtkRequisition  req;
688                 gint            window_height;
689                 GtkWidget      *dialog;
690                 GtkAllocation  *allocation;
691
692                 /* Save the window's size */
693                 dialog = empathy_chat_window_get_dialog (priv->window);
694                 gtk_window_get_size (GTK_WINDOW (dialog),
695                                      NULL, &window_height);
696
697                 gtk_widget_size_request (chat->input_text_view, &req);
698
699                 allocation = &GTK_WIDGET (chat->view)->allocation;
700
701                 priv->default_window_height = window_height;
702                 priv->last_input_height = req.height;
703                 priv->padding_height = window_height - req.height - allocation->height;
704
705                 chat->is_first_char = FALSE;
706         }
707
708         gtk_text_buffer_get_start_iter (buffer, &start);
709
710         if (!spell_checker) {
711                 gtk_text_buffer_get_end_iter (buffer, &end);
712                 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
713                 return;
714         }
715
716         if (!empathy_spell_supported ()) {
717                 return;
718         }
719
720         /* NOTE: this is really inefficient, we shouldn't have to
721            reiterate the whole buffer each time and check each work
722            every time. */
723         while (TRUE) {
724                 gboolean correct = FALSE;
725
726                 /* if at start */
727                 if (gtk_text_iter_is_start (&start)) {
728                         end = start;
729
730                         if (!gtk_text_iter_forward_word_end (&end)) {
731                                 /* no whole word yet */
732                                 break;
733                         }
734                 } else {
735                         if (!gtk_text_iter_forward_word_end (&end)) {
736                                 /* must be the end of the buffer */
737                                 break;
738                         }
739
740                         start = end;
741                         gtk_text_iter_backward_word_start (&start);
742                 }
743
744                 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
745
746                 /* spell check string */
747                 if (!empathy_chat_get_is_command (str)) {
748                         correct = empathy_spell_check (str);
749                 } else {
750                         correct = TRUE;
751                 }
752
753                 if (!correct) {
754                         gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
755                 } else {
756                         gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
757                 }
758
759                 g_free (str);
760
761                 /* set start iter to the end iters position */
762                 start = end;
763         }
764 }
765
766 typedef struct {
767         GtkWidget *window;
768         gint       width;
769         gint       height;
770 } ChangeSizeData;
771
772 static gboolean
773 chat_change_size_in_idle_cb (ChangeSizeData *data)
774 {
775         gtk_window_resize (GTK_WINDOW (data->window),
776                            data->width, data->height);
777
778         return FALSE;
779 }
780
781 static void
782 chat_text_view_scroll_hide_cb (GtkWidget  *widget,
783                                EmpathyChat *chat)
784 {
785         EmpathyChatPriv *priv;
786         GtkWidget      *sw;
787
788         priv = GET_PRIV (chat);
789
790         priv->vscroll_visible = FALSE;
791         g_signal_handlers_disconnect_by_func (widget, chat_text_view_scroll_hide_cb, chat);
792
793         sw = gtk_widget_get_parent (chat->input_text_view);
794         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
795                                         GTK_POLICY_NEVER,
796                                         GTK_POLICY_NEVER);
797         g_object_set (sw, "height-request", -1, NULL);
798 }
799
800 static void
801 chat_text_view_size_allocate_cb (GtkWidget     *widget,
802                                  GtkAllocation *allocation,
803                                  EmpathyChat    *chat)
804 {
805         EmpathyChatPriv *priv;
806         gint            width;
807         GtkWidget      *dialog;
808         ChangeSizeData *data;
809         gint            window_height;
810         gint            new_height;
811         GtkAllocation  *view_allocation;
812         gint            current_height;
813         gint            diff;
814         GtkWidget      *sw;
815
816         priv = GET_PRIV (chat);
817
818         if (priv->default_window_height <= 0) {
819                 return;
820         }
821
822         sw = gtk_widget_get_parent (widget);
823         if (sw->allocation.height >= MAX_INPUT_HEIGHT && !priv->vscroll_visible) {
824                 GtkWidget *vscroll;
825
826                 priv->vscroll_visible = TRUE;
827                 gtk_widget_set_size_request (sw, sw->allocation.width, MAX_INPUT_HEIGHT);
828                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
829                                                 GTK_POLICY_NEVER,
830                                                 GTK_POLICY_AUTOMATIC);
831                 vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (sw));
832                 g_signal_connect (vscroll, "hide",
833                                   G_CALLBACK (chat_text_view_scroll_hide_cb),
834                                   chat);
835         }
836
837         if (priv->last_input_height <= allocation->height) {
838                 priv->last_input_height = allocation->height;
839                 return;
840         }
841
842         diff = priv->last_input_height - allocation->height;
843         priv->last_input_height = allocation->height;
844
845         view_allocation = &GTK_WIDGET (chat->view)->allocation;
846
847         dialog = empathy_chat_window_get_dialog (priv->window);
848         gtk_window_get_size (GTK_WINDOW (dialog), NULL, &current_height);
849
850         new_height = view_allocation->height + priv->padding_height + allocation->height - diff;
851
852         if (new_height <= priv->default_window_height) {
853                 window_height = priv->default_window_height;
854         } else {
855                 window_height = new_height;
856         }
857
858         if (current_height <= window_height) {
859                 return;
860         }
861
862         /* Restore the window's size */
863         gtk_window_get_size (GTK_WINDOW (dialog), &width, NULL);
864
865         data = g_new0 (ChangeSizeData, 1);
866         data->window = dialog;
867         data->width  = width;
868         data->height = window_height;
869
870         g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
871                          (GSourceFunc) chat_change_size_in_idle_cb,
872                          data, g_free);
873 }
874
875 static void
876 chat_text_view_realize_cb (GtkWidget  *widget,
877                            EmpathyChat *chat)
878 {
879         empathy_debug (DEBUG_DOMAIN, "Setting focus to the input text view");
880         gtk_widget_grab_focus (widget);
881 }
882
883 static void
884 chat_insert_smiley_activate_cb (GtkWidget   *menuitem,
885                                 EmpathyChat *chat)
886 {
887         GtkTextBuffer *buffer;
888         GtkTextIter    iter;
889         const gchar   *smiley;
890
891         smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
892
893         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
894
895         gtk_text_buffer_get_end_iter (buffer, &iter);
896         gtk_text_buffer_insert (buffer, &iter, smiley, -1);
897
898         gtk_text_buffer_get_end_iter (buffer, &iter);
899         gtk_text_buffer_insert (buffer, &iter, " ", -1);
900 }
901
902 static void
903 chat_text_populate_popup_cb (GtkTextView *view,
904                              GtkMenu     *menu,
905                              EmpathyChat  *chat)
906 {
907         EmpathyChatPriv  *priv;
908         GtkTextBuffer   *buffer;
909         GtkTextTagTable *table;
910         GtkTextTag      *tag;
911         gint             x, y;
912         GtkTextIter      iter, start, end;
913         GtkWidget       *item;
914         gchar           *str = NULL;
915         EmpathyChatSpell *chat_spell;
916         GtkWidget       *smiley_menu;
917
918         priv = GET_PRIV (chat);
919
920         /* Add the emoticon menu. */
921         item = gtk_separator_menu_item_new ();
922         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
923         gtk_widget_show (item);
924
925         item = gtk_menu_item_new_with_mnemonic (_("Insert Smiley"));
926         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
927         gtk_widget_show (item);
928
929         smiley_menu = empathy_chat_view_get_smiley_menu (
930                 G_CALLBACK (chat_insert_smiley_activate_cb),
931                 chat);
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_seconds (
1033                 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                        TpChannelChatState  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_PAUSED:
1107         case TP_CHANNEL_CHAT_STATE_ACTIVE:
1108                 /* Contact is not composing */
1109                 if (l) {
1110                         priv->compositors = g_list_remove_link (priv->compositors, l);
1111                         g_object_unref (l->data);
1112                         g_list_free1 (l);
1113                 }
1114                 break;
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 const gchar *
1407 empathy_chat_get_id (EmpathyChat *chat)
1408 {
1409         EmpathyChatPriv *priv;
1410
1411         priv = GET_PRIV (chat);
1412
1413         return priv->id;
1414 }
1415
1416 void
1417 empathy_chat_clear (EmpathyChat *chat)
1418 {
1419         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1420
1421         empathy_chat_view_clear (chat->view);
1422 }
1423
1424 void
1425 empathy_chat_set_window (EmpathyChat       *chat,
1426                         EmpathyChatWindow *window)
1427 {
1428         EmpathyChatPriv *priv;
1429
1430         priv = GET_PRIV (chat);
1431         priv->window = window;
1432 }
1433
1434 EmpathyChatWindow *
1435 empathy_chat_get_window (EmpathyChat *chat)
1436 {
1437         EmpathyChatPriv *priv;
1438
1439         priv = GET_PRIV (chat);
1440
1441         return priv->window;
1442 }
1443
1444 void
1445 empathy_chat_scroll_down (EmpathyChat *chat)
1446 {
1447         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1448
1449         empathy_chat_view_scroll_down (chat->view);
1450 }
1451
1452 void
1453 empathy_chat_cut (EmpathyChat *chat)
1454 {
1455         GtkTextBuffer *buffer;
1456
1457         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1458
1459         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1460         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1461                 GtkClipboard *clipboard;
1462
1463                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1464
1465                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
1466         }
1467 }
1468
1469 void
1470 empathy_chat_copy (EmpathyChat *chat)
1471 {
1472         GtkTextBuffer *buffer;
1473
1474         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1475
1476         if (empathy_chat_view_get_selection_bounds (chat->view, NULL, NULL)) {
1477                 empathy_chat_view_copy_clipboard (chat->view);
1478                 return;
1479         }
1480
1481         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1482         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1483                 GtkClipboard *clipboard;
1484
1485                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1486
1487                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
1488         }
1489 }
1490
1491 void
1492 empathy_chat_paste (EmpathyChat *chat)
1493 {
1494         GtkTextBuffer *buffer;
1495         GtkClipboard  *clipboard;
1496
1497         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1498
1499         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1500         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1501
1502         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
1503 }
1504
1505 void
1506 empathy_chat_present (EmpathyChat *chat)
1507 {
1508         EmpathyChatPriv *priv;
1509
1510         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1511
1512         priv = GET_PRIV (chat);
1513
1514         if (priv->window == NULL) {
1515                 EmpathyChatWindow *window;
1516
1517                 window = empathy_chat_window_get_default ();
1518                 if (!window) {
1519                         window = empathy_chat_window_new ();
1520                 }
1521
1522                 empathy_chat_window_add_chat (window, chat);
1523         }
1524
1525         empathy_chat_window_switch_to_chat (priv->window, chat);
1526         empathy_window_present (
1527                 GTK_WINDOW (empathy_chat_window_get_dialog (priv->window)),
1528                 TRUE);
1529
1530         gtk_widget_grab_focus (chat->input_text_view); 
1531 }
1532
1533 gboolean
1534 empathy_chat_should_play_sound (EmpathyChat *chat)
1535 {
1536         EmpathyChatWindow *window;
1537         gboolean          play = TRUE;
1538
1539         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
1540
1541         window = empathy_chat_get_window (chat);
1542         if (!window) {
1543                 return TRUE;
1544         }
1545
1546         play = !empathy_chat_window_has_focus (window);
1547
1548         return play;
1549 }
1550
1551 gboolean
1552 empathy_chat_should_highlight_nick (EmpathyMessage *message)
1553 {
1554         EmpathyContact *contact;
1555         const gchar   *msg, *to;
1556         gchar         *cf_msg, *cf_to;
1557         gchar         *ch;
1558         gboolean       ret_val;
1559
1560         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
1561
1562         empathy_debug (DEBUG_DOMAIN, "Highlighting nickname");
1563
1564         ret_val = FALSE;
1565
1566         msg = empathy_message_get_body (message);
1567         if (!msg) {
1568                 return FALSE;
1569         }
1570
1571         contact = empathy_message_get_receiver (message);
1572         if (!contact || !empathy_contact_is_user (contact)) {
1573                 return FALSE;
1574         }
1575
1576         to = empathy_contact_get_name (contact);
1577         if (!to) {
1578                 return FALSE;
1579         }
1580
1581         cf_msg = g_utf8_casefold (msg, -1);
1582         cf_to = g_utf8_casefold (to, -1);
1583
1584         ch = strstr (cf_msg, cf_to);
1585         if (ch == NULL) {
1586                 goto finished;
1587         }
1588
1589         if (ch != cf_msg) {
1590                 /* Not first in the message */
1591                 if ((*(ch - 1) != ' ') &&
1592                     (*(ch - 1) != ',') &&
1593                     (*(ch - 1) != '.')) {
1594                         goto finished;
1595                 }
1596         }
1597
1598         ch = ch + strlen (cf_to);
1599         if (ch >= cf_msg + strlen (cf_msg)) {
1600                 ret_val = TRUE;
1601                 goto finished;
1602         }
1603
1604         if ((*ch == ' ') ||
1605             (*ch == ',') ||
1606             (*ch == '.') ||
1607             (*ch == ':')) {
1608                 ret_val = TRUE;
1609                 goto finished;
1610         }
1611
1612 finished:
1613         g_free (cf_msg);
1614         g_free (cf_to);
1615
1616         return ret_val;
1617 }
1618