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