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