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