]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-chat.c
e8906dbdd3a1ce29bbe11684da8ec39b38cac7bb
[empathy.git] / libempathy-gtk / gossip-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 #include <glade/glade.h>
37
38 #include <libempathy/empathy-contact-manager.h>
39 #include <libempathy/gossip-debug.h>
40 #include <libempathy/gossip-utils.h>
41 #include <libempathy/gossip-conf.h>
42
43 #include "gossip-chat.h"
44 #include "gossip-chat-window.h"
45 #include "gossip-geometry.h"
46 #include "gossip-preferences.h"
47 #include "gossip-spell.h"
48 //#include "gossip-spell-dialog.h"
49 #include "gossip-ui-utils.h"
50
51 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT, GossipChatPriv))
52
53 #define DEBUG_DOMAIN "Chat"
54
55 #define CHAT_DIR_CREATE_MODE  (S_IRUSR | S_IWUSR | S_IXUSR)
56 #define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
57
58 #define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
59
60 #define MAX_INPUT_HEIGHT 150
61
62 #define COMPOSING_STOP_TIMEOUT 5
63
64 struct _GossipChatPriv {
65         EmpathyContactManager *manager;
66         EmpathyTpChat         *tp_chat;
67         GossipChatWindow      *window;
68
69         GtkTooltips           *tooltips;
70         guint                  composing_stop_timeout_id;
71         gboolean               sensitive;
72         gchar                 *id;
73         GSList                *sent_messages;
74         gint                   sent_messages_index;
75         /* Used to automatically shrink a window that has temporarily
76          * grown due to long input. 
77          */
78         gint                   padding_height;
79         gint                   default_window_height;
80         gint                   last_input_height;
81         gboolean               vscroll_visible;
82 };
83
84 typedef struct {
85         GossipChat  *chat;
86         gchar       *word;
87
88         GtkTextIter  start;
89         GtkTextIter  end;
90 } GossipChatSpell;
91
92 static void             gossip_chat_class_init            (GossipChatClass *klass);
93 static void             gossip_chat_init                  (GossipChat      *chat);
94 static void             chat_finalize                     (GObject         *object);
95 static void             chat_destroy_cb                   (EmpathyTpChat   *tp_chat,
96                                                            GossipChat      *chat);
97 static void             chat_send                         (GossipChat      *chat,
98                                                            const gchar     *msg);
99 static void             chat_input_text_view_send         (GossipChat      *chat);
100 static void             chat_message_received_cb          (EmpathyTpChat   *tp_chat,
101                                                            GossipMessage   *message,
102                                                            GossipChat      *chat);
103 void                    chat_sent_message_add             (GossipChat      *chat,
104                                                            const gchar     *str);
105 const gchar *           chat_sent_message_get_next        (GossipChat      *chat);
106 const gchar *           chat_sent_message_get_last        (GossipChat      *chat);
107 static gboolean         chat_input_key_press_event_cb     (GtkWidget       *widget,
108                                                            GdkEventKey     *event,
109                                                            GossipChat      *chat);
110 static void             chat_input_text_buffer_changed_cb (GtkTextBuffer   *buffer,
111                                                            GossipChat      *chat);
112 static gboolean         chat_text_view_focus_in_event_cb  (GtkWidget       *widget,
113                                                            GdkEvent        *event,
114                                                            GossipChat      *chat);
115 static void             chat_text_view_scroll_hide_cb     (GtkWidget       *widget,
116                                                            GossipChat      *chat);
117 static void             chat_text_view_size_allocate_cb   (GtkWidget       *widget,
118                                                            GtkAllocation   *allocation,
119                                                            GossipChat      *chat);
120 static void             chat_text_view_realize_cb         (GtkWidget       *widget,
121                                                            GossipChat      *chat);
122 static void             chat_text_populate_popup_cb       (GtkTextView     *view,
123                                                            GtkMenu         *menu,
124                                                            GossipChat      *chat);
125 static void             chat_text_check_word_spelling_cb  (GtkMenuItem     *menuitem,
126                                                            GossipChatSpell *chat_spell);
127 static GossipChatSpell *chat_spell_new                    (GossipChat      *chat,
128                                                            const gchar     *word,
129                                                            GtkTextIter      start,
130                                                            GtkTextIter      end);
131 static void             chat_spell_free                   (GossipChatSpell *chat_spell);
132 static void             chat_composing_start              (GossipChat      *chat);
133 static void             chat_composing_stop               (GossipChat      *chat);
134 static void             chat_composing_remove_timeout     (GossipChat      *chat);
135 static gboolean         chat_composing_stop_timeout_cb    (GossipChat      *chat);
136
137 enum {
138         COMPOSING,
139         NEW_MESSAGE,
140         NAME_CHANGED,
141         STATUS_CHANGED,
142         LAST_SIGNAL
143 };
144
145 static guint chat_signals[LAST_SIGNAL] = { 0 };
146
147 G_DEFINE_TYPE (GossipChat, gossip_chat, G_TYPE_OBJECT);
148
149 static void
150 gossip_chat_class_init (GossipChatClass *klass)
151 {
152         GObjectClass *object_class;
153
154         object_class = G_OBJECT_CLASS (klass);
155
156         object_class->finalize = chat_finalize;
157
158         chat_signals[COMPOSING] =
159                 g_signal_new ("composing",
160                               G_OBJECT_CLASS_TYPE (object_class),
161                               G_SIGNAL_RUN_LAST,
162                               0,
163                               NULL, NULL,
164                               g_cclosure_marshal_VOID__BOOLEAN,
165                               G_TYPE_NONE,
166                               1, G_TYPE_BOOLEAN);
167
168         chat_signals[NEW_MESSAGE] =
169                 g_signal_new ("new-message",
170                               G_OBJECT_CLASS_TYPE (object_class),
171                               G_SIGNAL_RUN_LAST,
172                               0,
173                               NULL, NULL,
174                               g_cclosure_marshal_VOID__OBJECT,
175                               G_TYPE_NONE,
176                               1, GOSSIP_TYPE_MESSAGE);
177
178         chat_signals[NAME_CHANGED] =
179                 g_signal_new ("name-changed",
180                               G_OBJECT_CLASS_TYPE (object_class),
181                               G_SIGNAL_RUN_LAST,
182                               0,
183                               NULL, NULL,
184                               g_cclosure_marshal_VOID__POINTER,
185                               G_TYPE_NONE,
186                               1, G_TYPE_POINTER);
187
188         chat_signals[STATUS_CHANGED] =
189                 g_signal_new ("status-changed",
190                               G_OBJECT_CLASS_TYPE (object_class),
191                               G_SIGNAL_RUN_LAST,
192                               0,
193                               NULL, NULL,
194                               g_cclosure_marshal_VOID__VOID,
195                               G_TYPE_NONE,
196                               0);
197
198         g_type_class_add_private (object_class, sizeof (GossipChatPriv));
199 }
200
201 static void
202 gossip_chat_init (GossipChat *chat)
203 {
204         GossipChatPriv *priv;
205         GtkTextBuffer  *buffer;
206
207         chat->view = gossip_chat_view_new ();
208         chat->input_text_view = gtk_text_view_new ();
209
210         chat->is_first_char = TRUE;
211
212         g_object_set (chat->input_text_view,
213                       "pixels-above-lines", 2,
214                       "pixels-below-lines", 2,
215                       "pixels-inside-wrap", 1,
216                       "right-margin", 2,
217                       "left-margin", 2,
218                       "wrap-mode", GTK_WRAP_WORD_CHAR,
219                       NULL);
220
221         priv = GET_PRIV (chat);
222
223         priv->manager = empathy_contact_manager_new ();
224         priv->tooltips = gtk_tooltips_new ();
225         priv->default_window_height = -1;
226         priv->vscroll_visible = FALSE;
227         priv->sensitive = TRUE;
228         priv->sent_messages = NULL;
229         priv->sent_messages_index = -1;
230
231         g_signal_connect (chat->input_text_view,
232                           "key_press_event",
233                           G_CALLBACK (chat_input_key_press_event_cb),
234                           chat);
235
236         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
237         g_signal_connect (buffer,
238                           "changed",
239                           G_CALLBACK (chat_input_text_buffer_changed_cb),
240                           chat);
241         g_signal_connect (chat->view,
242                           "focus_in_event",
243                           G_CALLBACK (chat_text_view_focus_in_event_cb),
244                           chat);
245
246         g_signal_connect (chat->input_text_view,
247                           "size_allocate",
248                           G_CALLBACK (chat_text_view_size_allocate_cb),
249                           chat);
250
251         g_signal_connect (chat->input_text_view,
252                           "realize",
253                           G_CALLBACK (chat_text_view_realize_cb),
254                           chat);
255
256         g_signal_connect (GTK_TEXT_VIEW (chat->input_text_view),
257                           "populate_popup",
258                           G_CALLBACK (chat_text_populate_popup_cb),
259                           chat);
260
261         /* create misspelt words identification tag */
262         gtk_text_buffer_create_tag (buffer,
263                                     "misspelled",
264                                     "underline", PANGO_UNDERLINE_ERROR,
265                                     NULL);
266 }
267
268 static void
269 chat_finalize (GObject *object)
270 {
271         GossipChat     *chat;
272         GossipChatPriv *priv;
273
274         chat = GOSSIP_CHAT (object);
275         priv = GET_PRIV (chat);
276
277         gossip_debug (DEBUG_DOMAIN, "Finalized: %p", object);
278
279         g_slist_foreach (priv->sent_messages, (GFunc) g_free, NULL);
280         g_slist_free (priv->sent_messages);
281
282         chat_composing_remove_timeout (chat);
283         g_object_unref (chat->account);
284         g_object_unref (priv->manager);
285
286         if (priv->tp_chat) {
287                 g_object_unref (priv->tp_chat);
288         }
289
290         g_free (priv->id);
291
292         G_OBJECT_CLASS (gossip_chat_parent_class)->finalize (object);
293 }
294
295 static void
296 chat_destroy_cb (EmpathyTpChat *tp_chat,
297                  GossipChat    *chat)
298 {
299         GossipChatPriv *priv;
300         GtkWidget      *widget;
301
302         priv = GET_PRIV (chat);
303
304         if (priv->tp_chat) {
305                 g_object_unref (priv->tp_chat);
306                 priv->tp_chat = NULL;
307         }
308
309         gossip_chat_view_append_event (chat->view, _("Disconnected"));
310
311         widget = gossip_chat_get_widget (chat);
312         gtk_widget_set_sensitive (widget, FALSE);
313         priv->sensitive = FALSE;
314 }
315
316 static void
317 chat_send (GossipChat  *chat,
318            const gchar *msg)
319 {
320         GossipChatPriv   *priv;
321         //GossipLogManager *log_manager;
322         GossipMessage    *message;
323         GossipContact    *own_contact;
324
325         priv = GET_PRIV (chat);
326
327         if (G_STR_EMPTY (msg)) {
328                 return;
329         }
330
331         chat_sent_message_add (chat, msg);
332
333         if (g_str_has_prefix (msg, "/clear")) {
334                 gossip_chat_view_clear (chat->view);
335                 return;
336         }
337
338         /* FIXME: gossip_app_set_not_away ();*/
339
340         own_contact = gossip_chat_get_own_contact (chat);
341         message = gossip_message_new (msg);
342         gossip_message_set_sender (message, own_contact);
343
344         //FIXME: log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
345         //gossip_log_message_for_contact (log_manager, message, FALSE);
346
347         empathy_tp_chat_send (priv->tp_chat, message);
348
349         g_object_unref (message);
350 }
351
352 static void
353 chat_input_text_view_send (GossipChat *chat)
354 {
355         GossipChatPriv *priv;
356         GtkTextBuffer  *buffer;
357         GtkTextIter     start, end;
358         gchar          *msg;
359
360         priv = GET_PRIV (chat);
361
362         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
363
364         gtk_text_buffer_get_bounds (buffer, &start, &end);
365         msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
366
367         /* clear the input field */
368         gtk_text_buffer_set_text (buffer, "", -1);
369
370         chat_send (chat, msg);
371
372         g_free (msg);
373
374         chat->is_first_char = TRUE;
375 }
376
377 static void
378 chat_message_received_cb (EmpathyTpChat *tp_chat,
379                           GossipMessage *message,
380                           GossipChat    *chat)
381 {
382         GossipChatPriv *priv;
383         //GossipLogManager      *log_manager;
384         GossipContact         *sender;
385
386         priv = GET_PRIV (chat);
387
388         sender = gossip_message_get_sender (message);
389         gossip_debug (DEBUG_DOMAIN, "Appending message ('%s')",
390                       gossip_contact_get_name (sender));
391
392 /*FIXME:
393         log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
394         gossip_log_message_for_contact (log_manager, message, TRUE);
395 */
396         gossip_chat_view_append_message (chat->view, message);
397
398         if (gossip_chat_should_play_sound (chat)) {
399                 // FIXME: gossip_sound_play (GOSSIP_SOUND_CHAT);
400         }
401
402         g_signal_emit_by_name (chat, "new-message", message);
403 }
404
405 void 
406 chat_sent_message_add (GossipChat  *chat,
407                        const gchar *str)
408 {
409         GossipChatPriv *priv;
410         GSList         *list;
411         GSList         *item;
412
413         priv = GET_PRIV (chat);
414
415         /* Save the sent message in our repeat buffer */
416         list = priv->sent_messages;
417         
418         /* Remove any other occurances of this msg */
419         while ((item = g_slist_find_custom (list, str, (GCompareFunc) strcmp)) != NULL) {
420                 list = g_slist_remove_link (list, item);
421                 g_free (item->data);
422                 g_slist_free1 (item);
423         }
424
425         /* Trim the list to the last 10 items */
426         while (g_slist_length (list) > 10) {
427                 item = g_slist_last (list);
428                 if (item) {
429                         list = g_slist_remove_link (list, item);
430                         g_free (item->data);
431                         g_slist_free1 (item);
432                 }
433         }
434
435         /* Add new message */
436         list = g_slist_prepend (list, g_strdup (str));
437
438         /* Set list and reset the index */
439         priv->sent_messages = list;
440         priv->sent_messages_index = -1;
441 }
442
443 const gchar *
444 chat_sent_message_get_next (GossipChat *chat)
445 {
446         GossipChatPriv *priv;
447         gint            max;
448         
449         priv = GET_PRIV (chat);
450
451         if (!priv->sent_messages) {
452                 gossip_debug (DEBUG_DOMAIN, 
453                               "No sent messages, next message is NULL");
454                 return NULL;
455         }
456
457         max = g_slist_length (priv->sent_messages) - 1;
458
459         if (priv->sent_messages_index < max) {
460                 priv->sent_messages_index++;
461         }
462         
463         gossip_debug (DEBUG_DOMAIN, 
464                       "Returning next message index:%d",
465                       priv->sent_messages_index);
466
467         return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
468 }
469
470 const gchar *
471 chat_sent_message_get_last (GossipChat *chat)
472 {
473         GossipChatPriv *priv;
474
475         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
476
477         priv = GET_PRIV (chat);
478         
479         if (!priv->sent_messages) {
480                 gossip_debug (DEBUG_DOMAIN, 
481                               "No sent messages, last message is NULL");
482                 return NULL;
483         }
484
485         if (priv->sent_messages_index >= 0) {
486                 priv->sent_messages_index--;
487         }
488
489         gossip_debug (DEBUG_DOMAIN, 
490                       "Returning last message index:%d",
491                       priv->sent_messages_index);
492
493         return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
494 }
495
496 static gboolean
497 chat_input_key_press_event_cb (GtkWidget   *widget,
498                                GdkEventKey *event,
499                                GossipChat  *chat)
500 {
501         GossipChatPriv *priv;
502         GtkAdjustment  *adj;
503         gdouble         val;
504         GtkWidget      *text_view_sw;
505
506         priv = GET_PRIV (chat);
507
508         if (event->keyval == GDK_Tab && !(event->state & GDK_CONTROL_MASK)) {
509                 return TRUE;
510         }
511
512         /* Catch ctrl+up/down so we can traverse messages we sent */
513         if ((event->state & GDK_CONTROL_MASK) && 
514             (event->keyval == GDK_Up || 
515              event->keyval == GDK_Down)) {
516                 GtkTextBuffer *buffer;
517                 const gchar   *str;
518
519                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
520
521                 if (event->keyval == GDK_Up) {
522                         str = chat_sent_message_get_next (chat);
523                 } else {
524                         str = chat_sent_message_get_last (chat);
525                 }
526
527                 g_signal_handlers_block_by_func (buffer, 
528                                                  chat_input_text_buffer_changed_cb,
529                                                  chat);
530                 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
531                 g_signal_handlers_unblock_by_func (buffer, 
532                                                    chat_input_text_buffer_changed_cb,
533                                                    chat);
534
535                 return TRUE;    
536         }
537
538         /* Catch enter but not ctrl/shift-enter */
539         if (IS_ENTER (event->keyval) && !(event->state & GDK_SHIFT_MASK)) {
540                 GtkTextView *view;
541
542                 /* This is to make sure that kinput2 gets the enter. And if
543                  * it's handled there we shouldn't send on it. This is because
544                  * kinput2 uses Enter to commit letters. See:
545                  * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
546                  */
547
548                 view = GTK_TEXT_VIEW (chat->input_text_view);
549                 if (gtk_im_context_filter_keypress (view->im_context, event)) {
550                         GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
551                         return TRUE;
552                 }
553
554                 chat_input_text_view_send (chat);
555                 return TRUE;
556         }
557
558         text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
559
560         if (IS_ENTER (event->keyval) && (event->state & GDK_SHIFT_MASK)) {
561                 /* Newline for shift-enter. */
562                 return FALSE;
563         }
564         else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
565                  event->keyval == GDK_Page_Up) {
566                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
567                 gtk_adjustment_set_value (adj, adj->value - adj->page_size);
568
569                 return TRUE;
570         }
571         else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
572                  event->keyval == GDK_Page_Down) {
573                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
574                 val = MIN (adj->value + adj->page_size, adj->upper - adj->page_size);
575                 gtk_adjustment_set_value (adj, val);
576
577                 return TRUE;
578         }
579
580         return FALSE;
581 }
582
583 static gboolean
584 chat_text_view_focus_in_event_cb (GtkWidget  *widget,
585                                   GdkEvent   *event,
586                                   GossipChat *chat)
587 {
588         gtk_widget_grab_focus (chat->input_text_view);
589
590         return TRUE;
591 }
592
593 static void
594 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
595                                    GossipChat    *chat)
596 {
597         GossipChatPriv *priv;
598         GtkTextIter     start, end;
599         gchar          *str;
600         gboolean        spell_checker = FALSE;
601
602         priv = GET_PRIV (chat);
603
604         if (gtk_text_buffer_get_char_count (buffer) == 0) {
605                 chat_composing_stop (chat);
606         } else {
607                 chat_composing_start (chat);
608         }
609
610         gossip_conf_get_bool (gossip_conf_get (),
611                               GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED,
612                               &spell_checker);
613
614         if (chat->is_first_char) {
615                 GtkRequisition  req;
616                 gint            window_height;
617                 GtkWidget      *dialog;
618                 GtkAllocation  *allocation;
619
620                 /* Save the window's size */
621                 dialog = gossip_chat_window_get_dialog (priv->window);
622                 gtk_window_get_size (GTK_WINDOW (dialog),
623                                      NULL, &window_height);
624
625                 gtk_widget_size_request (chat->input_text_view, &req);
626
627                 allocation = &GTK_WIDGET (chat->view)->allocation;
628
629                 priv->default_window_height = window_height;
630                 priv->last_input_height = req.height;
631                 priv->padding_height = window_height - req.height - allocation->height;
632
633                 chat->is_first_char = FALSE;
634         }
635
636         gtk_text_buffer_get_start_iter (buffer, &start);
637
638         if (!spell_checker) {
639                 gtk_text_buffer_get_end_iter (buffer, &end);
640                 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
641                 return;
642         }
643
644         if (!gossip_spell_supported ()) {
645                 return;
646         }
647
648         /* NOTE: this is really inefficient, we shouldn't have to
649            reiterate the whole buffer each time and check each work
650            every time. */
651         while (TRUE) {
652                 gboolean correct = FALSE;
653
654                 /* if at start */
655                 if (gtk_text_iter_is_start (&start)) {
656                         end = start;
657
658                         if (!gtk_text_iter_forward_word_end (&end)) {
659                                 /* no whole word yet */
660                                 break;
661                         }
662                 } else {
663                         if (!gtk_text_iter_forward_word_end (&end)) {
664                                 /* must be the end of the buffer */
665                                 break;
666                         }
667
668                         start = end;
669                         gtk_text_iter_backward_word_start (&start);
670                 }
671
672                 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
673
674                 /* spell check string */
675                 if (!gossip_chat_get_is_command (str)) {
676                         correct = gossip_spell_check (str);
677                 } else {
678                         correct = TRUE;
679                 }
680
681                 if (!correct) {
682                         gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
683                 } else {
684                         gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
685                 }
686
687                 g_free (str);
688
689                 /* set start iter to the end iters position */
690                 start = end;
691         }
692 }
693
694 typedef struct {
695         GtkWidget *window;
696         gint       width;
697         gint       height;
698 } ChangeSizeData;
699
700 static gboolean
701 chat_change_size_in_idle_cb (ChangeSizeData *data)
702 {
703         gtk_window_resize (GTK_WINDOW (data->window),
704                            data->width, data->height);
705
706         return FALSE;
707 }
708
709 static void
710 chat_text_view_scroll_hide_cb (GtkWidget  *widget,
711                                GossipChat *chat)
712 {
713         GossipChatPriv *priv;
714         GtkWidget      *sw;
715
716         priv = GET_PRIV (chat);
717
718         priv->vscroll_visible = FALSE;
719         g_signal_handlers_disconnect_by_func (widget, chat_text_view_scroll_hide_cb, chat);
720
721         sw = gtk_widget_get_parent (chat->input_text_view);
722         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
723                                         GTK_POLICY_NEVER,
724                                         GTK_POLICY_NEVER);
725         g_object_set (sw, "height-request", -1, NULL);
726 }
727
728 static void
729 chat_text_view_size_allocate_cb (GtkWidget     *widget,
730                                  GtkAllocation *allocation,
731                                  GossipChat    *chat)
732 {
733         GossipChatPriv *priv;
734         gint            width;
735         GtkWidget      *dialog;
736         ChangeSizeData *data;
737         gint            window_height;
738         gint            new_height;
739         GtkAllocation  *view_allocation;
740         gint            current_height;
741         gint            diff;
742         GtkWidget      *sw;
743
744         priv = GET_PRIV (chat);
745
746         if (priv->default_window_height <= 0) {
747                 return;
748         }
749
750         sw = gtk_widget_get_parent (widget);
751         if (sw->allocation.height >= MAX_INPUT_HEIGHT && !priv->vscroll_visible) {
752                 GtkWidget *vscroll;
753
754                 priv->vscroll_visible = TRUE;
755                 gtk_widget_set_size_request (sw, sw->allocation.width, MAX_INPUT_HEIGHT);
756                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
757                                                 GTK_POLICY_NEVER,
758                                                 GTK_POLICY_AUTOMATIC);
759                 vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (sw));
760                 g_signal_connect (vscroll, "hide",
761                                   G_CALLBACK (chat_text_view_scroll_hide_cb),
762                                   chat);
763         }
764
765         if (priv->last_input_height <= allocation->height) {
766                 priv->last_input_height = allocation->height;
767                 return;
768         }
769
770         diff = priv->last_input_height - allocation->height;
771         priv->last_input_height = allocation->height;
772
773         view_allocation = &GTK_WIDGET (chat->view)->allocation;
774
775         dialog = gossip_chat_window_get_dialog (priv->window);
776         gtk_window_get_size (GTK_WINDOW (dialog), NULL, &current_height);
777
778         new_height = view_allocation->height + priv->padding_height + allocation->height - diff;
779
780         if (new_height <= priv->default_window_height) {
781                 window_height = priv->default_window_height;
782         } else {
783                 window_height = new_height;
784         }
785
786         if (current_height <= window_height) {
787                 return;
788         }
789
790         /* Restore the window's size */
791         gtk_window_get_size (GTK_WINDOW (dialog), &width, NULL);
792
793         data = g_new0 (ChangeSizeData, 1);
794         data->window = dialog;
795         data->width  = width;
796         data->height = window_height;
797
798         g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
799                          (GSourceFunc) chat_change_size_in_idle_cb,
800                          data, g_free);
801 }
802
803 static void
804 chat_text_view_realize_cb (GtkWidget  *widget,
805                            GossipChat *chat)
806 {
807         gossip_debug (DEBUG_DOMAIN, "Setting focus to the input text view");
808         gtk_widget_grab_focus (widget);
809 }
810
811 static void
812 chat_insert_smiley_activate_cb (GtkWidget  *menuitem,
813                                 GossipChat *chat)
814 {
815         GtkTextBuffer *buffer;
816         GtkTextIter    iter;
817         const gchar   *smiley;
818
819         smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
820
821         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
822
823         gtk_text_buffer_get_end_iter (buffer, &iter);
824         gtk_text_buffer_insert (buffer, &iter, smiley, -1);
825
826         gtk_text_buffer_get_end_iter (buffer, &iter);
827         gtk_text_buffer_insert (buffer, &iter, " ", -1);
828 }
829
830 static void
831 chat_text_populate_popup_cb (GtkTextView *view,
832                              GtkMenu     *menu,
833                              GossipChat  *chat)
834 {
835         GossipChatPriv  *priv;
836         GtkTextBuffer   *buffer;
837         GtkTextTagTable *table;
838         GtkTextTag      *tag;
839         gint             x, y;
840         GtkTextIter      iter, start, end;
841         GtkWidget       *item;
842         gchar           *str = NULL;
843         GossipChatSpell *chat_spell;
844         GtkWidget       *smiley_menu;
845
846         priv = GET_PRIV (chat);
847
848         /* Add the emoticon menu. */
849         item = gtk_separator_menu_item_new ();
850         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
851         gtk_widget_show (item);
852
853         item = gtk_menu_item_new_with_mnemonic (_("Insert Smiley"));
854         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
855         gtk_widget_show (item);
856
857         smiley_menu = gossip_chat_view_get_smiley_menu (
858                 G_CALLBACK (chat_insert_smiley_activate_cb),
859                 chat,
860                 priv->tooltips);
861         gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
862
863         /* Add the spell check menu item. */
864         buffer = gtk_text_view_get_buffer (view);
865         table = gtk_text_buffer_get_tag_table (buffer);
866
867         tag = gtk_text_tag_table_lookup (table, "misspelled");
868
869         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
870
871         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
872                                                GTK_TEXT_WINDOW_WIDGET,
873                                                x, y,
874                                                &x, &y);
875
876         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
877
878         start = end = iter;
879
880         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
881             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
882
883                 str = gtk_text_buffer_get_text (buffer,
884                                                 &start, &end, FALSE);
885         }
886
887         if (G_STR_EMPTY (str)) {
888                 return;
889         }
890
891         chat_spell = chat_spell_new (chat, str, start, end);
892
893         g_object_set_data_full (G_OBJECT (menu),
894                                 "chat_spell", chat_spell,
895                                 (GDestroyNotify) chat_spell_free);
896
897         item = gtk_separator_menu_item_new ();
898         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
899         gtk_widget_show (item);
900
901         item = gtk_menu_item_new_with_mnemonic (_("_Check Word Spelling..."));
902         g_signal_connect (item,
903                           "activate",
904                           G_CALLBACK (chat_text_check_word_spelling_cb),
905                           chat_spell);
906         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
907         gtk_widget_show (item);
908 }
909
910 static void
911 chat_text_check_word_spelling_cb (GtkMenuItem     *menuitem,
912                                   GossipChatSpell *chat_spell)
913 {
914 /*FIXME:        gossip_spell_dialog_show (chat_spell->chat,
915                                   chat_spell->start,
916                                   chat_spell->end,
917                                   chat_spell->word);*/
918 }
919
920 static GossipChatSpell *
921 chat_spell_new (GossipChat  *chat,
922                 const gchar *word,
923                 GtkTextIter  start,
924                 GtkTextIter  end)
925 {
926         GossipChatSpell *chat_spell;
927
928         chat_spell = g_new0 (GossipChatSpell, 1);
929
930         chat_spell->chat = g_object_ref (chat);
931         chat_spell->word = g_strdup (word);
932         chat_spell->start = start;
933         chat_spell->end = end;
934
935         return chat_spell;
936 }
937
938 static void
939 chat_spell_free (GossipChatSpell *chat_spell)
940 {
941         g_object_unref (chat_spell->chat);
942         g_free (chat_spell->word);
943         g_free (chat_spell);
944 }
945
946 static void
947 chat_composing_start (GossipChat *chat)
948 {
949         GossipChatPriv *priv;
950
951         priv = GET_PRIV (chat);
952
953         if (priv->composing_stop_timeout_id) {
954                 /* Just restart the timeout */
955                 chat_composing_remove_timeout (chat);
956         } else {
957         /* FIXME:
958                 gossip_session_send_composing (gossip_app_get_session (),
959                                                priv->contact, TRUE);
960                                               */
961         }
962
963         priv->composing_stop_timeout_id = g_timeout_add (
964                 1000 * COMPOSING_STOP_TIMEOUT,
965                 (GSourceFunc) chat_composing_stop_timeout_cb,
966                 chat);
967 }
968
969 static void
970 chat_composing_stop (GossipChat *chat)
971 {
972         GossipChatPriv *priv;
973
974         priv = GET_PRIV (chat);
975
976         chat_composing_remove_timeout (chat);
977         /* FIXME:
978         gossip_session_send_composing (gossip_app_get_session (),
979                                        priv->contact, FALSE);*/
980 }
981
982 static void
983 chat_composing_remove_timeout (GossipChat *chat)
984 {
985         GossipChatPriv *priv;
986
987         priv = GET_PRIV (chat);
988
989         if (priv->composing_stop_timeout_id) {
990                 g_source_remove (priv->composing_stop_timeout_id);
991                 priv->composing_stop_timeout_id = 0;
992         }
993 }
994
995 static gboolean
996 chat_composing_stop_timeout_cb (GossipChat *chat)
997 {
998         GossipChatPriv *priv;
999
1000         priv = GET_PRIV (chat);
1001
1002         priv->composing_stop_timeout_id = 0;
1003         /* FIXME:
1004         gossip_session_send_composing (gossip_app_get_session (),
1005                                        priv->contact, FALSE);*/
1006
1007         return FALSE;
1008 }
1009
1010 gboolean
1011 gossip_chat_get_is_command (const gchar *str)
1012 {
1013         g_return_val_if_fail (str != NULL, FALSE);
1014
1015         if (str[0] != '/') {
1016                 return FALSE;
1017         }
1018
1019         if (g_str_has_prefix (str, "/me")) {
1020                 return TRUE;
1021         }
1022         else if (g_str_has_prefix (str, "/nick")) {
1023                 return TRUE;
1024         }
1025         else if (g_str_has_prefix (str, "/topic")) {
1026                 return TRUE;
1027         }
1028
1029         return FALSE;
1030 }
1031
1032 void
1033 gossip_chat_correct_word (GossipChat  *chat,
1034                           GtkTextIter  start,
1035                           GtkTextIter  end,
1036                           const gchar *new_word)
1037 {
1038         GtkTextBuffer *buffer;
1039
1040         g_return_if_fail (chat != NULL);
1041         g_return_if_fail (new_word != NULL);
1042
1043         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1044
1045         gtk_text_buffer_delete (buffer, &start, &end);
1046         gtk_text_buffer_insert (buffer, &start,
1047                                 new_word,
1048                                 -1);
1049 }
1050
1051 const gchar *
1052 gossip_chat_get_name (GossipChat *chat)
1053 {
1054         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
1055
1056         if (GOSSIP_CHAT_GET_CLASS (chat)->get_name) {
1057                 return GOSSIP_CHAT_GET_CLASS (chat)->get_name (chat);
1058         }
1059
1060         return NULL;
1061 }
1062
1063 gchar *
1064 gossip_chat_get_tooltip (GossipChat *chat)
1065 {
1066         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
1067
1068         if (GOSSIP_CHAT_GET_CLASS (chat)->get_tooltip) {
1069                 return GOSSIP_CHAT_GET_CLASS (chat)->get_tooltip (chat);
1070         }
1071
1072         return NULL;
1073 }
1074
1075 const gchar *
1076 gossip_chat_get_status_icon_name (GossipChat *chat)
1077 {
1078         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
1079
1080         if (GOSSIP_CHAT_GET_CLASS (chat)->get_status_icon_name) {
1081                 return GOSSIP_CHAT_GET_CLASS (chat)->get_status_icon_name (chat);
1082         }
1083
1084         return NULL;
1085 }
1086
1087 GossipContact *
1088 gossip_chat_get_contact (GossipChat *chat)
1089 {
1090         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
1091
1092         if (GOSSIP_CHAT_GET_CLASS (chat)->get_contact) {
1093                 return GOSSIP_CHAT_GET_CLASS (chat)->get_contact (chat);
1094         }
1095
1096         return NULL;
1097 }
1098 GossipContact *
1099 gossip_chat_get_own_contact (GossipChat *chat)
1100 {
1101         GossipChatPriv *priv;
1102
1103         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
1104
1105         priv = GET_PRIV (chat);
1106
1107         return empathy_contact_manager_get_own (priv->manager, chat->account);
1108 }
1109
1110 GtkWidget *
1111 gossip_chat_get_widget (GossipChat *chat)
1112 {
1113         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
1114
1115         if (GOSSIP_CHAT_GET_CLASS (chat)->get_widget) {
1116                 return GOSSIP_CHAT_GET_CLASS (chat)->get_widget (chat);
1117         }
1118
1119         return NULL;
1120 }
1121
1122 gboolean
1123 gossip_chat_is_group_chat (GossipChat *chat)
1124 {
1125         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
1126
1127         if (GOSSIP_CHAT_GET_CLASS (chat)->is_group_chat) {
1128                 return GOSSIP_CHAT_GET_CLASS (chat)->is_group_chat (chat);
1129         }
1130
1131         return FALSE;
1132 }
1133
1134 gboolean 
1135 gossip_chat_is_connected (GossipChat *chat)
1136 {
1137         GossipChatPriv *priv;
1138
1139         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
1140
1141         priv = GET_PRIV (chat);
1142
1143         return (priv->tp_chat != NULL);
1144 }
1145
1146 gboolean
1147 gossip_chat_get_show_contacts (GossipChat *chat)
1148 {
1149         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
1150
1151         if (GOSSIP_CHAT_GET_CLASS (chat)->get_show_contacts) {
1152                 return GOSSIP_CHAT_GET_CLASS (chat)->get_show_contacts (chat);
1153         }
1154
1155         return FALSE;
1156 }
1157
1158 void
1159 gossip_chat_set_show_contacts (GossipChat *chat,
1160                                gboolean    show)
1161 {
1162         g_return_if_fail (GOSSIP_IS_CHAT (chat));
1163
1164         if (GOSSIP_CHAT_GET_CLASS (chat)->set_show_contacts) {
1165                 GOSSIP_CHAT_GET_CLASS (chat)->set_show_contacts (chat, show);
1166         }
1167 }
1168
1169 void
1170 gossip_chat_save_geometry (GossipChat *chat,
1171                            gint        x,
1172                            gint        y,
1173                            gint        w,
1174                            gint        h)
1175 {
1176         gossip_geometry_save (gossip_chat_get_id (chat), x, y, w, h);
1177 }
1178
1179 void
1180 gossip_chat_load_geometry (GossipChat *chat,
1181                            gint       *x,
1182                            gint       *y,
1183                            gint       *w,
1184                            gint       *h)
1185 {
1186         gossip_geometry_load (gossip_chat_get_id (chat), x, y, w, h);
1187 }
1188
1189 void
1190 gossip_chat_set_tp_chat (GossipChat    *chat,
1191                          EmpathyTpChat *tp_chat)
1192 {
1193         GossipChatPriv *priv;
1194         GtkWidget      *widget;
1195
1196         g_return_if_fail (GOSSIP_IS_CHAT (chat));
1197         g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
1198
1199         priv = GET_PRIV (chat);
1200
1201         if (tp_chat == priv->tp_chat) {
1202                 return;
1203         }
1204
1205         if (priv->tp_chat) {
1206                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1207                                                       chat_message_received_cb,
1208                                                       chat);
1209                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1210                                                       chat_destroy_cb,
1211                                                       chat);
1212                 g_object_unref (priv->tp_chat);
1213         }
1214
1215         g_free (priv->id);
1216         priv->tp_chat = g_object_ref (tp_chat);
1217         priv->id = g_strdup (empathy_tp_chat_get_id (tp_chat));
1218
1219         g_signal_connect (tp_chat, "message-received",
1220                           G_CALLBACK (chat_message_received_cb),
1221                           chat);
1222         g_signal_connect (tp_chat, "destroy",
1223                           G_CALLBACK (chat_destroy_cb),
1224                           chat);
1225
1226         empathy_tp_chat_request_pending (tp_chat);
1227
1228         if (!priv->sensitive) {
1229                 widget = gossip_chat_get_widget (chat);
1230                 gtk_widget_set_sensitive (widget, TRUE);
1231                 gossip_chat_view_append_event (chat->view, _("Connected"));
1232                 priv->sensitive = TRUE;
1233         }
1234 }
1235
1236 const gchar *
1237 gossip_chat_get_id (GossipChat *chat)
1238 {
1239         GossipChatPriv *priv;
1240
1241         priv = GET_PRIV (chat);
1242
1243         return priv->id;
1244 }
1245
1246 void
1247 gossip_chat_clear (GossipChat *chat)
1248 {
1249         g_return_if_fail (GOSSIP_IS_CHAT (chat));
1250
1251         gossip_chat_view_clear (chat->view);
1252 }
1253
1254 void
1255 gossip_chat_set_window (GossipChat       *chat,
1256                         GossipChatWindow *window)
1257 {
1258         GossipChatPriv *priv;
1259
1260         priv = GET_PRIV (chat);
1261         priv->window = window;
1262 }
1263
1264 GossipChatWindow *
1265 gossip_chat_get_window (GossipChat *chat)
1266 {
1267         GossipChatPriv *priv;
1268
1269         priv = GET_PRIV (chat);
1270
1271         return priv->window;
1272 }
1273
1274 void
1275 gossip_chat_scroll_down (GossipChat *chat)
1276 {
1277         g_return_if_fail (GOSSIP_IS_CHAT (chat));
1278
1279         gossip_chat_view_scroll_down (chat->view);
1280 }
1281
1282 void
1283 gossip_chat_cut (GossipChat *chat)
1284 {
1285         GtkTextBuffer *buffer;
1286
1287         g_return_if_fail (GOSSIP_IS_CHAT (chat));
1288
1289         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1290         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1291                 GtkClipboard *clipboard;
1292
1293                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1294
1295                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
1296         }
1297 }
1298
1299 void
1300 gossip_chat_copy (GossipChat *chat)
1301 {
1302         GtkTextBuffer *buffer;
1303
1304         g_return_if_fail (GOSSIP_IS_CHAT (chat));
1305
1306         if (gossip_chat_view_get_selection_bounds (chat->view, NULL, NULL)) {
1307                 gossip_chat_view_copy_clipboard (chat->view);
1308                 return;
1309         }
1310
1311         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1312         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1313                 GtkClipboard *clipboard;
1314
1315                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1316
1317                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
1318         }
1319 }
1320
1321 void
1322 gossip_chat_paste (GossipChat *chat)
1323 {
1324         GtkTextBuffer *buffer;
1325         GtkClipboard  *clipboard;
1326
1327         g_return_if_fail (GOSSIP_IS_CHAT (chat));
1328
1329         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1330         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1331
1332         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
1333 }
1334
1335 void
1336 gossip_chat_present (GossipChat *chat)
1337 {
1338         GossipChatPriv *priv;
1339
1340         g_return_if_fail (GOSSIP_IS_CHAT (chat));
1341
1342         priv = GET_PRIV (chat);
1343
1344         if (priv->window == NULL) {
1345                 GossipChatWindow *window;
1346
1347                 window = gossip_chat_window_get_default ();
1348                 if (!window) {
1349                         window = gossip_chat_window_new ();
1350                 }
1351
1352                 gossip_chat_window_add_chat (window, chat);
1353         }
1354
1355         gossip_chat_window_switch_to_chat (priv->window, chat);
1356         gossip_window_present (
1357                 GTK_WINDOW (gossip_chat_window_get_dialog (priv->window)),
1358                 TRUE);
1359
1360         gtk_widget_grab_focus (chat->input_text_view); 
1361 }
1362
1363 gboolean
1364 gossip_chat_should_play_sound (GossipChat *chat)
1365 {
1366         GossipChatWindow *window;
1367         gboolean          play = TRUE;
1368
1369         g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
1370
1371         window = gossip_chat_get_window (chat);
1372         if (!window) {
1373                 return TRUE;
1374         }
1375
1376         play = !gossip_chat_window_has_focus (window);
1377
1378         return play;
1379 }
1380
1381 gboolean
1382 gossip_chat_should_highlight_nick (GossipMessage *message)
1383 {
1384         GossipContact *my_contact;
1385         const gchar   *msg, *to;
1386         gchar         *cf_msg, *cf_to;
1387         gchar         *ch;
1388         gboolean       ret_val;
1389
1390         g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), FALSE);
1391
1392         gossip_debug (DEBUG_DOMAIN, "Highlighting nickname");
1393
1394         ret_val = FALSE;
1395
1396         msg = gossip_message_get_body (message);
1397         if (!msg) {
1398                 return FALSE;
1399         }
1400
1401         my_contact = gossip_get_own_contact_from_contact (gossip_message_get_sender (message));
1402         to = gossip_contact_get_name (my_contact);
1403         if (!to) {
1404                 return FALSE;
1405         }
1406
1407         cf_msg = g_utf8_casefold (msg, -1);
1408         cf_to = g_utf8_casefold (to, -1);
1409
1410         ch = strstr (cf_msg, cf_to);
1411         if (ch == NULL) {
1412                 goto finished;
1413         }
1414
1415         if (ch != cf_msg) {
1416                 /* Not first in the message */
1417                 if ((*(ch - 1) != ' ') &&
1418                     (*(ch - 1) != ',') &&
1419                     (*(ch - 1) != '.')) {
1420                         goto finished;
1421                 }
1422         }
1423
1424         ch = ch + strlen (cf_to);
1425         if (ch >= cf_msg + strlen (cf_msg)) {
1426                 ret_val = TRUE;
1427                 goto finished;
1428         }
1429
1430         if ((*ch == ' ') ||
1431             (*ch == ',') ||
1432             (*ch == '.') ||
1433             (*ch == ':')) {
1434                 ret_val = TRUE;
1435                 goto finished;
1436         }
1437
1438 finished:
1439         g_free (cf_msg);
1440         g_free (cf_to);
1441
1442         return ret_val;
1443 }
1444