]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat-text-view.c
chat_log_filter: don't leak EmpathyMessage
[empathy.git] / libempathy-gtk / empathy-chat-text-view.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) 2008 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., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Richard Hult <richard@imendio.com>
23  *          Martyn Russell <martyn@imendio.com>
24  *          Xavier Claessens <xclaesse@gmail.com>
25  */
26
27 #include "config.h"
28
29 #include <sys/types.h>
30 #include <string.h>
31 #include <time.h>
32
33 #include <glib/gi18n-lib.h>
34 #include <gtk/gtk.h>
35
36 #include <telepathy-glib/util.h>
37
38 #include <libempathy/empathy-gsettings.h>
39 #include <libempathy/empathy-utils.h>
40
41 #include "empathy-chat-text-view.h"
42 #include "empathy-chat.h"
43 #include "empathy-ui-utils.h"
44 #include "empathy-smiley-manager.h"
45 #include "empathy-string-parser.h"
46
47 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
48 #include <libempathy/empathy-debug.h>
49
50 /* Number of seconds between timestamps when using normal mode, 5 minutes. */
51 #define TIMESTAMP_INTERVAL 300
52
53 #define MAX_LINES 800
54 #define MAX_SCROLL_TIME 0.4 /* seconds */
55 #define SCROLL_DELAY 33     /* milliseconds */
56
57 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatTextView)
58
59 typedef struct {
60         GtkTextBuffer        *buffer;
61         guint                 scroll_timeout;
62         GTimer               *scroll_time;
63         GtkTextMark          *find_mark_previous;
64         GtkTextMark          *find_mark_next;
65         gboolean              find_wrapped;
66         gboolean              find_last_direction;
67         EmpathyContact       *last_contact;
68         time_t                last_timestamp;
69         gboolean              allow_scrolling;
70         guint                 notify_system_fonts_id;
71         GSettings            *gsettings;
72         EmpathySmileyManager *smiley_manager;
73         gboolean              only_if_date;
74 } EmpathyChatTextViewPriv;
75
76 static void chat_text_view_iface_init (EmpathyChatViewIface *iface);
77
78 static void chat_text_view_copy_clipboard (EmpathyChatView *view);
79
80 G_DEFINE_TYPE_WITH_CODE (EmpathyChatTextView, empathy_chat_text_view,
81                          GTK_TYPE_TEXT_VIEW,
82                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
83                                                 chat_text_view_iface_init));
84
85 enum {
86         PROP_0,
87         PROP_LAST_CONTACT,
88         PROP_ONLY_IF_DATE
89 };
90
91 static gboolean
92 chat_text_view_url_event_cb (GtkTextTag          *tag,
93                              GObject             *object,
94                              GdkEvent            *event,
95                              GtkTextIter         *iter,
96                              EmpathyChatTextView *view)
97 {
98         EmpathyChatTextViewPriv *priv;
99         GtkTextIter              start, end;
100         gchar                   *str;
101
102         priv = GET_PRIV (view);
103
104         /* If the link is being selected, don't do anything. */
105         gtk_text_buffer_get_selection_bounds (priv->buffer, &start, &end);
106         if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
107                 return FALSE;
108         }
109
110         if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
111                 start = end = *iter;
112
113                 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
114                     gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
115                             str = gtk_text_buffer_get_text (priv->buffer,
116                                                             &start,
117                                                             &end,
118                                                             FALSE);
119
120                             empathy_url_show (GTK_WIDGET (view), str);
121                             g_free (str);
122                     }
123         }
124
125         return FALSE;
126 }
127
128 static gboolean
129 chat_text_view_event_cb (EmpathyChatTextView *view,
130                          GdkEventMotion      *event,
131                          GtkTextTag          *tag)
132 {
133         static GdkCursor  *hand = NULL;
134         static GdkCursor  *beam = NULL;
135         GtkTextWindowType  type;
136         GtkTextIter        iter;
137         GdkWindow         *win;
138         gint               x, y, buf_x, buf_y;
139
140         type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
141                                               event->window);
142
143         if (type != GTK_TEXT_WINDOW_TEXT) {
144                 return FALSE;
145         }
146
147         /* Get where the pointer really is. */
148         win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
149         if (!win) {
150                 return FALSE;
151         }
152
153         gdk_window_get_pointer (win, &x, &y, NULL);
154
155         /* Get the iter where the cursor is at */
156         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
157                                                x, y,
158                                                &buf_x, &buf_y);
159
160         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
161                                             &iter,
162                                             buf_x, buf_y);
163
164         if (gtk_text_iter_has_tag (&iter, tag)) {
165                 if (!hand) {
166                         hand = gdk_cursor_new (GDK_HAND2);
167                         beam = gdk_cursor_new (GDK_XTERM);
168                 }
169                 gdk_window_set_cursor (win, hand);
170         } else {
171                 if (!beam) {
172                         beam = gdk_cursor_new (GDK_XTERM);
173                 }
174                 gdk_window_set_cursor (win, beam);
175         }
176
177         return FALSE;
178 }
179
180 static void
181 chat_text_view_create_tags (EmpathyChatTextView *view)
182 {
183         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
184         GtkTextTag              *tag;
185
186         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_CUT, NULL);
187         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_HIGHLIGHT, NULL);
188         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_SPACING, NULL);
189         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_TIME, NULL);
190         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_ACTION, NULL);
191         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_BODY, NULL);
192         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_EVENT, NULL);
193
194         tag = gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_LINK, NULL);
195         g_signal_connect (tag, "event",
196                           G_CALLBACK (chat_text_view_url_event_cb),
197                           view);
198
199         g_signal_connect (view, "motion-notify-event",
200                           G_CALLBACK (chat_text_view_event_cb),
201                           tag);
202 }
203
204 static void
205 chat_text_view_system_font_update (EmpathyChatTextView *view)
206 {
207         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
208         PangoFontDescription *font_description = NULL;
209         gchar                *font_name;
210
211         font_name = g_settings_get_string (priv->gsettings,
212                         EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
213
214         if (font_name != NULL) {
215                 font_description = pango_font_description_from_string (font_name);
216                 g_free (font_name);
217         } else {
218                 font_description = NULL;
219         }
220
221         gtk_widget_modify_font (GTK_WIDGET (view), font_description);
222
223         if (font_description) {
224                 pango_font_description_free (font_description);
225         }
226 }
227
228 static void
229 chat_text_view_notify_system_font_cb (GSettings *gsettings,
230                                       const gchar *key,
231                                       EmpathyChatTextView *self)
232 {
233         chat_text_view_system_font_update (self);
234 }
235
236 static void
237 chat_text_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
238 {
239         empathy_url_show (GTK_WIDGET (menuitem), url);
240 }
241
242 static void
243 chat_text_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
244 {
245         GtkClipboard *clipboard;
246
247         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
248         gtk_clipboard_set_text (clipboard, url, -1);
249
250         clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
251         gtk_clipboard_set_text (clipboard, url, -1);
252 }
253
254 static void
255 chat_text_view_populate_popup (EmpathyChatTextView *view,
256                                GtkMenu        *menu,
257                                gpointer        user_data)
258 {
259         EmpathyChatTextViewPriv *priv;
260         GtkTextTagTable    *table;
261         GtkTextTag         *tag;
262         gint                x, y;
263         GtkTextIter         iter, start, end;
264         GtkWidget          *item;
265         gchar              *str = NULL;
266
267         priv = GET_PRIV (view);
268
269         /* Clear menu item */
270         if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
271                 item = gtk_separator_menu_item_new ();
272                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
273                 gtk_widget_show (item);
274
275                 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
276                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
277                 gtk_widget_show (item);
278
279                 g_signal_connect_swapped (item, "activate",
280                                           G_CALLBACK (empathy_chat_view_clear),
281                                           view);
282         }
283
284         /* Link context menu items */
285         table = gtk_text_buffer_get_tag_table (priv->buffer);
286         tag = gtk_text_tag_table_lookup (table, EMPATHY_CHAT_TEXT_VIEW_TAG_LINK);
287
288         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
289
290         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
291                                                GTK_TEXT_WINDOW_WIDGET,
292                                                x, y,
293                                                &x, &y);
294
295         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
296
297         start = end = iter;
298
299         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
300             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
301                     str = gtk_text_buffer_get_text (priv->buffer,
302                                                     &start, &end, FALSE);
303             }
304
305         if (EMP_STR_EMPTY (str)) {
306                 g_free (str);
307                 return;
308         }
309
310         /* NOTE: Set data just to get the string freed when not needed. */
311         g_object_set_data_full (G_OBJECT (menu),
312                                 "url", str,
313                                 (GDestroyNotify) g_free);
314
315         item = gtk_separator_menu_item_new ();
316         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
317         gtk_widget_show (item);
318
319         item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
320         g_signal_connect (item, "activate",
321                           G_CALLBACK (chat_text_view_copy_address_cb),
322                           str);
323         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
324         gtk_widget_show (item);
325
326         item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
327         g_signal_connect (item, "activate",
328                           G_CALLBACK (chat_text_view_open_address_cb),
329                           str);
330         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
331         gtk_widget_show (item);
332 }
333
334 static gboolean
335 chat_text_view_is_scrolled_down (EmpathyChatTextView *view)
336 {
337         GtkAdjustment *vadj;
338         gdouble value;
339         gdouble upper;
340         gdouble page_size;
341
342         vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (view));
343         value = gtk_adjustment_get_value (vadj);
344         upper = gtk_adjustment_get_upper (vadj);
345         page_size = gtk_adjustment_get_page_size (vadj);
346
347         if (value < upper - page_size) {
348                 return FALSE;
349         }
350
351         return TRUE;
352 }
353
354 static void
355 chat_text_view_maybe_trim_buffer (EmpathyChatTextView *view)
356 {
357         EmpathyChatTextViewPriv *priv;
358         GtkTextIter         top, bottom;
359         gint                line;
360         gint                remove_;
361         GtkTextTagTable    *table;
362         GtkTextTag         *tag;
363
364         priv = GET_PRIV (view);
365
366         gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
367         line = gtk_text_iter_get_line (&bottom);
368         if (line < MAX_LINES) {
369                 return;
370         }
371
372         remove_ = line - MAX_LINES;
373         gtk_text_buffer_get_start_iter (priv->buffer, &top);
374
375         bottom = top;
376         if (!gtk_text_iter_forward_lines (&bottom, remove_)) {
377                 return;
378         }
379
380         /* Track backwords to a place where we can safely cut, we don't do it in
381           * the middle of a tag.
382           */
383         table = gtk_text_buffer_get_tag_table (priv->buffer);
384         tag = gtk_text_tag_table_lookup (table, EMPATHY_CHAT_TEXT_VIEW_TAG_CUT);
385         if (!tag) {
386                 return;
387         }
388
389         if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
390                 return;
391         }
392
393         if (!gtk_text_iter_equal (&top, &bottom)) {
394                 gtk_text_buffer_delete (priv->buffer, &top, &bottom);
395         }
396 }
397
398 static void
399 chat_text_view_append_timestamp (EmpathyChatTextView *view,
400                                  time_t               timestamp,
401                                  gboolean             show_date)
402 {
403         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
404         GtkTextIter              iter;
405         gchar                   *tmp;
406         GString                 *str;
407
408         str = g_string_new ("- ");
409
410         /* Append date if needed */
411         if (show_date) {
412                 GDate *date;
413                 gchar  buf[256];
414
415                 date = g_date_new ();
416                 g_date_set_time_t (date, timestamp);
417                 /* Translators: timestamp displayed between conversations in
418                  * chat windows (strftime format string) */
419                 g_date_strftime (buf, 256, _("%A %B %d %Y"), date);
420                 g_string_append (str, buf);
421                 g_string_append (str, ", ");
422                 g_date_free (date);
423         }
424
425         /* Append time */
426         tmp = empathy_time_to_string_local (timestamp, EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
427         g_string_append (str, tmp);
428         g_free (tmp);
429
430         g_string_append (str, " -\n");
431
432         /* Insert the string in the buffer */
433         empathy_chat_text_view_append_spacing (view);
434         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
435         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
436                                                   &iter,
437                                                   str->str, -1,
438                                                   EMPATHY_CHAT_TEXT_VIEW_TAG_TIME,
439                                                   NULL);
440
441         g_string_free (str, TRUE);
442 }
443
444 static void
445 chat_text_maybe_append_date_and_time (EmpathyChatTextView *view,
446                                       time_t               timestamp)
447 {
448         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
449         GDate                   *date, *last_date;
450         gboolean                 append_date = FALSE;
451         gboolean                 append_time = FALSE;
452
453         /* Get the date from last message */
454         last_date = g_date_new ();
455         g_date_set_time_t (last_date, priv->last_timestamp);
456
457         /* Get the date of the message we are appending */
458         date = g_date_new ();
459         g_date_set_time_t (date, timestamp);
460
461         /* If last message was from another day we append date and time */
462         if (g_date_compare (date, last_date) > 0) {
463                 append_date = TRUE;
464                 append_time = TRUE;
465         }
466
467         g_date_free (last_date);
468         g_date_free (date);
469
470         /* If last message is 'old' append the time */
471         if (timestamp - priv->last_timestamp >= TIMESTAMP_INTERVAL) {
472                 append_time = TRUE;
473         }
474
475         if (append_date || (!priv->only_if_date && append_time)) {
476                 chat_text_view_append_timestamp (view, timestamp, append_date);
477         }
478 }
479
480 static void
481 chat_text_view_size_allocate (GtkWidget     *widget,
482                               GtkAllocation *alloc)
483 {
484         gboolean down;
485
486         down = chat_text_view_is_scrolled_down (EMPATHY_CHAT_TEXT_VIEW (widget));
487
488         GTK_WIDGET_CLASS (empathy_chat_text_view_parent_class)->size_allocate (widget, alloc);
489
490         if (down) {
491                 GtkAdjustment *adj;
492
493                 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
494                 gtk_adjustment_set_value (adj,
495                                           gtk_adjustment_get_upper (adj) -
496                                           gtk_adjustment_get_page_size (adj));
497         }
498 }
499
500 static gboolean
501 chat_text_view_drag_motion (GtkWidget      *widget,
502                             GdkDragContext *context,
503                             gint            x,
504                             gint            y,
505                             guint           time_)
506 {
507         /* Don't handle drag motion, since we don't want the view to scroll as
508          * the result of dragging something across it. */
509
510         return FALSE;
511 }
512
513 static void
514 chat_text_view_get_property (GObject    *object,
515                              guint       param_id,
516                              GValue     *value,
517                              GParamSpec *pspec)
518 {
519         EmpathyChatTextViewPriv *priv = GET_PRIV (object);
520
521         switch (param_id) {
522         case PROP_LAST_CONTACT:
523                 g_value_set_object (value, priv->last_contact);
524                 break;
525         case PROP_ONLY_IF_DATE:
526                 g_value_set_boolean (value, priv->only_if_date);
527                 break;
528         default:
529                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
530                 break;
531         };
532 }
533
534 static void
535 chat_text_view_set_property (GObject      *object,
536                              guint         param_id,
537                              const GValue *value,
538                              GParamSpec   *pspec)
539 {
540         EmpathyChatTextViewPriv *priv = GET_PRIV (object);
541
542         switch (param_id) {
543         case PROP_ONLY_IF_DATE:
544                 priv->only_if_date = g_value_get_boolean (value);
545                 break;
546         default:
547                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
548                 break;
549         };
550 }
551
552 static void
553 chat_text_view_finalize (GObject *object)
554 {
555         EmpathyChatTextView     *view;
556         EmpathyChatTextViewPriv *priv;
557
558         view = EMPATHY_CHAT_TEXT_VIEW (object);
559         priv = GET_PRIV (view);
560
561         DEBUG ("%p", object);
562
563         g_object_unref (priv->gsettings);
564
565         if (priv->last_contact) {
566                 g_object_unref (priv->last_contact);
567         }
568         if (priv->scroll_time) {
569                 g_timer_destroy (priv->scroll_time);
570         }
571         if (priv->scroll_timeout) {
572                 g_source_remove (priv->scroll_timeout);
573         }
574         g_object_unref (priv->smiley_manager);
575
576         G_OBJECT_CLASS (empathy_chat_text_view_parent_class)->finalize (object);
577 }
578
579 static void
580 text_view_copy_clipboard (GtkTextView *text_view)
581 {
582         chat_text_view_copy_clipboard (EMPATHY_CHAT_VIEW (text_view));
583 }
584
585 static void
586 empathy_chat_text_view_class_init (EmpathyChatTextViewClass *klass)
587 {
588         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
589         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
590         GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
591
592         object_class->finalize = chat_text_view_finalize;
593         object_class->get_property = chat_text_view_get_property;
594         object_class->set_property = chat_text_view_set_property;
595
596         widget_class->size_allocate = chat_text_view_size_allocate;
597         widget_class->drag_motion = chat_text_view_drag_motion;
598
599         text_view_class->copy_clipboard = text_view_copy_clipboard;
600
601         g_object_class_install_property (object_class,
602                                          PROP_LAST_CONTACT,
603                                          g_param_spec_object ("last-contact",
604                                                               "Last contact",
605                                                               "The sender of the last received message",
606                                                               EMPATHY_TYPE_CONTACT,
607                                                               G_PARAM_READABLE));
608         g_object_class_install_property (object_class,
609                                          PROP_ONLY_IF_DATE,
610                                          g_param_spec_boolean ("only-if-date",
611                                                               "Only if date",
612                                                               "Display timestamp only if the date changes",
613                                                               FALSE,
614                                                               G_PARAM_READWRITE));
615
616
617         g_type_class_add_private (object_class, sizeof (EmpathyChatTextViewPriv));
618 }
619
620 static void
621 empathy_chat_text_view_init (EmpathyChatTextView *view)
622 {
623         EmpathyChatTextViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
624                 EMPATHY_TYPE_CHAT_TEXT_VIEW, EmpathyChatTextViewPriv);
625
626         view->priv = priv;
627         priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
628         priv->last_timestamp = 0;
629         priv->allow_scrolling = TRUE;
630         priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
631
632         g_object_set (view,
633                       "wrap-mode", GTK_WRAP_WORD_CHAR,
634                       "editable", FALSE,
635                       "cursor-visible", FALSE,
636                       NULL);
637
638         priv->gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
639         g_signal_connect (priv->gsettings,
640                           "changed::" EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME,
641                           G_CALLBACK (chat_text_view_notify_system_font_cb),
642                           view);
643         chat_text_view_system_font_update (view);
644         chat_text_view_create_tags (view);
645
646         g_signal_connect (view,
647                           "populate-popup",
648                           G_CALLBACK (chat_text_view_populate_popup),
649                           NULL);
650 }
651
652 static void
653 chat_text_view_scroll_stop (EmpathyChatTextView *view)
654 {
655         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
656
657         g_timer_destroy (priv->scroll_time);
658         priv->scroll_time = NULL;
659
660         g_source_remove (priv->scroll_timeout);
661         priv->scroll_timeout = 0;
662 }
663
664 /* Code stolen from pidgin/gtkimhtml.c */
665 static gboolean
666 chat_text_view_scroll_cb (EmpathyChatTextView *view)
667 {
668         EmpathyChatTextViewPriv *priv;
669         GtkAdjustment      *adj;
670         gdouble             max_val;
671
672         priv = GET_PRIV (view);
673
674         adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (view));
675         max_val = gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj);
676
677         g_return_val_if_fail (priv->scroll_time != NULL, FALSE);
678
679         if (g_timer_elapsed (priv->scroll_time, NULL) > MAX_SCROLL_TIME) {
680                 /* time's up. jump to the end and kill the timer */
681                 gtk_adjustment_set_value (adj, max_val);
682                 chat_text_view_scroll_stop (view);
683                 return FALSE;
684         }
685
686         /* scroll by 1/3rd the remaining distance */
687         gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) + ((max_val - gtk_adjustment_get_value (adj)) / 3));
688         return TRUE;
689 }
690
691 static void
692 chat_text_view_scroll_down (EmpathyChatView *view)
693 {
694         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
695
696         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
697
698         if (!priv->allow_scrolling) {
699                 return;
700         }
701
702         DEBUG ("Scrolling down");
703
704         if (priv->scroll_time) {
705                 g_timer_reset (priv->scroll_time);
706         } else {
707                 priv->scroll_time = g_timer_new ();
708         }
709         if (!priv->scroll_timeout) {
710                 priv->scroll_timeout = g_timeout_add (SCROLL_DELAY,
711                                                       (GSourceFunc) chat_text_view_scroll_cb,
712                                                       view);
713         }
714 }
715
716 static void
717 chat_text_view_append_message (EmpathyChatView *view,
718                                EmpathyMessage  *msg)
719 {
720         EmpathyChatTextView     *text_view = EMPATHY_CHAT_TEXT_VIEW (view);
721         EmpathyChatTextViewPriv *priv = GET_PRIV (text_view);
722         gboolean                 bottom;
723         time_t                   timestamp;
724
725         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
726         g_return_if_fail (EMPATHY_IS_MESSAGE (msg));
727
728         if (!empathy_message_get_body (msg)) {
729                 return;
730         }
731
732         bottom = chat_text_view_is_scrolled_down (text_view);
733
734         chat_text_view_maybe_trim_buffer (EMPATHY_CHAT_TEXT_VIEW (view));
735
736         timestamp = empathy_message_get_timestamp (msg);
737         chat_text_maybe_append_date_and_time (text_view, timestamp);
738         if (EMPATHY_CHAT_TEXT_VIEW_GET_CLASS (view)->append_message) {
739                 EMPATHY_CHAT_TEXT_VIEW_GET_CLASS (view)->append_message (text_view,
740                                                                          msg);
741         }
742
743         if (bottom) {
744                 chat_text_view_scroll_down (view);
745         }
746
747         if (priv->last_contact) {
748                 g_object_unref (priv->last_contact);
749         }
750         priv->last_contact = g_object_ref (empathy_message_get_sender (msg));
751         g_object_notify (G_OBJECT (view), "last-contact");
752
753         priv->last_timestamp = timestamp;
754 }
755
756 static void
757 chat_text_view_append_event (EmpathyChatView *view,
758                              const gchar     *str)
759 {
760         EmpathyChatTextView     *text_view = EMPATHY_CHAT_TEXT_VIEW (view);
761         EmpathyChatTextViewPriv *priv = GET_PRIV (text_view);
762         gboolean                 bottom;
763         GtkTextIter              iter;
764         gchar                   *msg;
765
766
767         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
768         g_return_if_fail (!EMP_STR_EMPTY (str));
769
770         bottom = chat_text_view_is_scrolled_down (text_view);
771         chat_text_view_maybe_trim_buffer (EMPATHY_CHAT_TEXT_VIEW (view));
772         chat_text_maybe_append_date_and_time (text_view,
773                                               empathy_time_get_current ());
774
775         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
776         msg = g_strdup_printf (" - %s\n", str);
777         gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
778                                                   msg, -1,
779                                                   EMPATHY_CHAT_TEXT_VIEW_TAG_EVENT,
780                                                   NULL);
781         g_free (msg);
782
783         if (bottom) {
784                 chat_text_view_scroll_down (view);
785         }
786
787         if (priv->last_contact) {
788                 g_object_unref (priv->last_contact);
789                 priv->last_contact = NULL;
790                 g_object_notify (G_OBJECT (view), "last-contact");
791         }
792 }
793
794 static void
795 chat_text_view_scroll (EmpathyChatView *view,
796                        gboolean         allow_scrolling)
797 {
798         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
799
800         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
801
802         DEBUG ("Scrolling %s", allow_scrolling ? "enabled" : "disabled");
803
804         priv->allow_scrolling = allow_scrolling;
805         if (allow_scrolling) {
806                 empathy_chat_view_scroll_down (view);
807         }
808 }
809
810 static gboolean
811 chat_text_view_get_has_selection (EmpathyChatView *view)
812 {
813         GtkTextBuffer *buffer;
814
815         g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view), FALSE);
816
817         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
818
819         return gtk_text_buffer_get_has_selection (buffer);
820 }
821
822 static void
823 chat_text_view_clear (EmpathyChatView *view)
824 {
825         GtkTextBuffer      *buffer;
826         EmpathyChatTextViewPriv *priv;
827
828         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
829
830         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
831         gtk_text_buffer_set_text (buffer, "", -1);
832
833         /* We set these back to the initial values so we get
834           * timestamps when clearing the window to know when
835           * conversations start.
836           */
837         priv = GET_PRIV (view);
838
839         priv->last_timestamp = 0;
840         if (priv->last_contact) {
841                 g_object_unref (priv->last_contact);
842                 priv->last_contact = NULL;
843         }
844 }
845
846 static gboolean
847 chat_text_view_find_previous (EmpathyChatView *view,
848                                 const gchar     *search_criteria,
849                                 gboolean         new_search,
850                                 gboolean         match_case)
851 {
852         EmpathyChatTextViewPriv *priv;
853         GtkTextBuffer      *buffer;
854         GtkTextIter         iter_at_mark;
855         GtkTextIter         iter_match_start;
856         GtkTextIter         iter_match_end;
857         gboolean            found;
858         gboolean            from_start = FALSE;
859
860         g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view), FALSE);
861         g_return_val_if_fail (search_criteria != NULL, FALSE);
862
863         priv = GET_PRIV (view);
864
865         buffer = priv->buffer;
866
867         if (EMP_STR_EMPTY (search_criteria)) {
868                 if (priv->find_mark_previous) {
869                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
870
871                         gtk_text_buffer_move_mark (buffer,
872                                                    priv->find_mark_previous,
873                                                    &iter_at_mark);
874                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
875                                                       priv->find_mark_previous,
876                                                       0.0,
877                                                       TRUE,
878                                                       0.0,
879                                                       0.0);
880                         gtk_text_buffer_select_range (buffer,
881                                                       &iter_at_mark,
882                                                       &iter_at_mark);
883                 }
884
885                 return FALSE;
886         }
887
888         if (new_search) {
889                 from_start = TRUE;
890         }
891
892         if (!new_search && priv->find_mark_previous) {
893                 gtk_text_buffer_get_iter_at_mark (buffer,
894                                                   &iter_at_mark,
895                                                   priv->find_mark_previous);
896         } else {
897                 gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
898                 from_start = TRUE;
899         }
900
901         priv->find_last_direction = FALSE;
902
903         /* Use the standard GTK+ method for case sensitive searches. It can't do
904          * case insensitive searches (see bug #61852), so keep the custom method
905          * around for case insensitive searches. */
906         if (match_case) {
907                 found = gtk_text_iter_backward_search (&iter_at_mark,
908                                                        search_criteria,
909                                                        0, /* no text search flags, we want exact matches */
910                                                        &iter_match_start,
911                                                        &iter_match_end,
912                                                        NULL);
913         } else {
914                 found = empathy_text_iter_backward_search (&iter_at_mark,
915                                                            search_criteria,
916                                                            &iter_match_start,
917                                                            &iter_match_end,
918                                                            NULL);
919         }
920
921         if (!found) {
922                 gboolean result = FALSE;
923
924                 if (from_start) {
925                         return result;
926                 }
927
928                 /* Here we wrap around. */
929                 if (!new_search && !priv->find_wrapped) {
930                         priv->find_wrapped = TRUE;
931                         result = chat_text_view_find_previous (view,
932                                                                  search_criteria,
933                                                                  FALSE,
934                                                                  match_case);
935                         priv->find_wrapped = FALSE;
936                 }
937
938                 return result;
939         }
940
941         /* Set new mark and show on screen */
942         if (!priv->find_mark_previous) {
943                 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
944                                                                         &iter_match_start,
945                                                                         TRUE);
946         } else {
947                 gtk_text_buffer_move_mark (buffer,
948                                            priv->find_mark_previous,
949                                            &iter_match_start);
950         }
951
952         if (!priv->find_mark_next) {
953                 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
954                                                                     &iter_match_end,
955                                                                     TRUE);
956         } else {
957                 gtk_text_buffer_move_mark (buffer,
958                                            priv->find_mark_next,
959                                            &iter_match_end);
960         }
961
962         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
963                                       priv->find_mark_previous,
964                                       0.0,
965                                       TRUE,
966                                       0.5,
967                                       0.5);
968
969         gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
970         gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
971
972         return TRUE;
973 }
974
975 static gboolean
976 chat_text_view_find_next (EmpathyChatView *view,
977                             const gchar     *search_criteria,
978                             gboolean         new_search,
979                             gboolean         match_case)
980 {
981         EmpathyChatTextViewPriv *priv;
982         GtkTextBuffer      *buffer;
983         GtkTextIter         iter_at_mark;
984         GtkTextIter         iter_match_start;
985         GtkTextIter         iter_match_end;
986         gboolean            found;
987         gboolean            from_start = FALSE;
988
989         g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view), FALSE);
990         g_return_val_if_fail (search_criteria != NULL, FALSE);
991
992         priv = GET_PRIV (view);
993
994         buffer = priv->buffer;
995
996         if (EMP_STR_EMPTY (search_criteria)) {
997                 if (priv->find_mark_next) {
998                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
999
1000                         gtk_text_buffer_move_mark (buffer,
1001                                                    priv->find_mark_next,
1002                                                    &iter_at_mark);
1003                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1004                                                       priv->find_mark_next,
1005                                                       0.0,
1006                                                       TRUE,
1007                                                       0.0,
1008                                                       0.0);
1009                         gtk_text_buffer_select_range (buffer,
1010                                                       &iter_at_mark,
1011                                                       &iter_at_mark);
1012                 }
1013
1014                 return FALSE;
1015         }
1016
1017         if (new_search) {
1018                 from_start = TRUE;
1019         }
1020
1021         if (!new_search && priv->find_mark_next) {
1022                 gtk_text_buffer_get_iter_at_mark (buffer,
1023                                                   &iter_at_mark,
1024                                                   priv->find_mark_next);
1025         } else {
1026                 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1027                 from_start = TRUE;
1028         }
1029
1030         priv->find_last_direction = TRUE;
1031
1032         /* Use the standard GTK+ method for case sensitive searches. It can't do
1033          * case insensitive searches (see bug #61852), so keep the custom method
1034          * around for case insensitive searches. */
1035         if (match_case) {
1036                 found = gtk_text_iter_forward_search (&iter_at_mark,
1037                                                       search_criteria,
1038                                                       0,
1039                                                       &iter_match_start,
1040                                                       &iter_match_end,
1041                                                       NULL);
1042         } else {
1043                 found = empathy_text_iter_forward_search (&iter_at_mark,
1044                                                           search_criteria,
1045                                                           &iter_match_start,
1046                                                           &iter_match_end,
1047                                                           NULL);
1048         }
1049
1050         if (!found) {
1051                 gboolean result = FALSE;
1052
1053                 if (from_start) {
1054                         return result;
1055                 }
1056
1057                 /* Here we wrap around. */
1058                 if (!new_search && !priv->find_wrapped) {
1059                         priv->find_wrapped = TRUE;
1060                         result = chat_text_view_find_next (view,
1061                                                              search_criteria,
1062                                                              FALSE,
1063                                                              match_case);
1064                         priv->find_wrapped = FALSE;
1065                 }
1066
1067                 return result;
1068         }
1069
1070         /* Set new mark and show on screen */
1071         if (!priv->find_mark_next) {
1072                 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1073                                                                     &iter_match_end,
1074                                                                     TRUE);
1075         } else {
1076                 gtk_text_buffer_move_mark (buffer,
1077                                            priv->find_mark_next,
1078                                            &iter_match_end);
1079         }
1080
1081         if (!priv->find_mark_previous) {
1082                 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1083                                                                         &iter_match_start,
1084                                                                         TRUE);
1085         } else {
1086                 gtk_text_buffer_move_mark (buffer,
1087                                            priv->find_mark_previous,
1088                                            &iter_match_start);
1089         }
1090
1091         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1092                                       priv->find_mark_next,
1093                                       0.0,
1094                                       TRUE,
1095                                       0.5,
1096                                       0.5);
1097
1098         gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1099         gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1100
1101         return TRUE;
1102 }
1103
1104 static void
1105 chat_text_view_find_abilities (EmpathyChatView *view,
1106                                  const gchar    *search_criteria,
1107                                  gboolean        match_case,
1108                                  gboolean       *can_do_previous,
1109                                  gboolean       *can_do_next)
1110 {
1111         EmpathyChatTextViewPriv *priv;
1112         GtkTextBuffer           *buffer;
1113         GtkTextIter              iter_at_mark;
1114         GtkTextIter              iter_match_start;
1115         GtkTextIter              iter_match_end;
1116
1117         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
1118         g_return_if_fail (search_criteria != NULL);
1119         g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
1120
1121         priv = GET_PRIV (view);
1122
1123         buffer = priv->buffer;
1124
1125         if (can_do_previous) {
1126                 if (priv->find_mark_previous) {
1127                         gtk_text_buffer_get_iter_at_mark (buffer,
1128                                                           &iter_at_mark,
1129                                                           priv->find_mark_previous);
1130                 } else {
1131                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1132                 }
1133
1134                 if (match_case) {
1135                         *can_do_previous = gtk_text_iter_backward_search (&iter_at_mark,
1136                                                                           search_criteria,
1137                                                                           0,
1138                                                                           &iter_match_start,
1139                                                                           &iter_match_end,
1140                                                                           NULL);
1141                 } else {
1142                         *can_do_previous = empathy_text_iter_backward_search (&iter_at_mark,
1143                                                                               search_criteria,
1144                                                                               &iter_match_start,
1145                                                                               &iter_match_end,
1146                                                                               NULL);
1147                 }
1148         }
1149
1150         if (can_do_next) {
1151                 if (priv->find_mark_next) {
1152                         gtk_text_buffer_get_iter_at_mark (buffer,
1153                                                           &iter_at_mark,
1154                                                           priv->find_mark_next);
1155                 } else {
1156                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1157                 }
1158
1159                 if (match_case) {
1160                         *can_do_next = gtk_text_iter_forward_search (&iter_at_mark,
1161                                                                      search_criteria,
1162                                                                      0,
1163                                                                      &iter_match_start,
1164                                                                      &iter_match_end,
1165                                                                      NULL);
1166                 } else {
1167                         *can_do_next = empathy_text_iter_forward_search (&iter_at_mark,
1168                                                                          search_criteria,
1169                                                                          &iter_match_start,
1170                                                                          &iter_match_end,
1171                                                                          NULL);
1172                 }
1173         }
1174 }
1175
1176 static void
1177 chat_text_view_highlight (EmpathyChatView *view,
1178                             const gchar     *text,
1179                             gboolean         match_case)
1180 {
1181         GtkTextBuffer *buffer;
1182         GtkTextIter    iter;
1183         GtkTextIter    iter_start;
1184         GtkTextIter    iter_end;
1185         GtkTextIter    iter_match_start;
1186         GtkTextIter    iter_match_end;
1187         gboolean       found;
1188
1189         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
1190
1191         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1192
1193         gtk_text_buffer_get_start_iter (buffer, &iter);
1194
1195         gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
1196         gtk_text_buffer_remove_tag_by_name (buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_HIGHLIGHT,
1197                                             &iter_start,
1198                                             &iter_end);
1199
1200         if (EMP_STR_EMPTY (text)) {
1201                 return;
1202         }
1203
1204         while (1) {
1205                 if (match_case) {
1206                         found = gtk_text_iter_forward_search (&iter,
1207                                                               text,
1208                                                               0,
1209                                                               &iter_match_start,
1210                                                               &iter_match_end,
1211                                                               NULL);
1212                 } else {
1213                         found = empathy_text_iter_forward_search (&iter,
1214                                                                   text,
1215                                                                   &iter_match_start,
1216                                                                   &iter_match_end,
1217                                                                   NULL);
1218                 }
1219                 if (!found) {
1220                         break;
1221                 }
1222
1223                 gtk_text_buffer_apply_tag_by_name (buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_HIGHLIGHT,
1224                                                    &iter_match_start,
1225                                                    &iter_match_end);
1226
1227                 iter = iter_match_end;
1228         }
1229 }
1230
1231 static void
1232 chat_text_view_copy_clipboard (EmpathyChatView *view)
1233 {
1234         GtkTextBuffer *buffer;
1235         GtkTextIter start, iter, end;
1236         GtkClipboard  *clipboard;
1237         GdkPixbuf *pixbuf;
1238         gunichar c;
1239         GtkTextChildAnchor *anchor = NULL;
1240         GString *str;
1241         GList *list;
1242         gboolean ignore_newlines = FALSE;
1243
1244         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
1245
1246         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1247         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1248
1249         if (!gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
1250                 return;
1251
1252         str = g_string_new ("");
1253
1254         for (iter = start; !gtk_text_iter_equal (&iter, &end); gtk_text_iter_forward_char (&iter)) {
1255                 c = gtk_text_iter_get_char (&iter);
1256                 /* 0xFFFC is the 'object replacement' unicode character,
1257                  * it indicates the presence of a pixbuf or a widget. */
1258                 if (c == 0xFFFC) {
1259                         ignore_newlines = FALSE;
1260                         if ((pixbuf = gtk_text_iter_get_pixbuf (&iter))) {
1261                                 gchar *text;
1262                                 text = g_object_get_data (G_OBJECT(pixbuf),
1263                                                           "smiley_str");
1264                                 if (text)
1265                                         str = g_string_append (str, text);
1266                         } else if ((anchor = gtk_text_iter_get_child_anchor (&iter))) {
1267                                 gchar *text;
1268                                 list = gtk_text_child_anchor_get_widgets (anchor);
1269                                 if (list) {
1270                                         text = g_object_get_data (G_OBJECT(list->data),
1271                                                                   "str_obj");
1272                                         if (text)
1273                                                 str = g_string_append (str, text);
1274                                 }
1275                                 g_list_free (list);
1276                         }
1277                 } else if (c == '\n') {
1278                         if (!ignore_newlines) {
1279                                 ignore_newlines = TRUE;
1280                                 str = g_string_append_unichar (str, c);
1281                         }
1282                 } else {
1283                         ignore_newlines = FALSE;
1284                         str = g_string_append_unichar (str, c);
1285                 }
1286         }
1287
1288         gtk_clipboard_set_text (clipboard, str->str, str->len);
1289         g_string_free (str, TRUE);
1290 }
1291
1292 static void
1293 chat_text_view_iface_init (EmpathyChatViewIface *iface)
1294 {
1295         iface->append_message = chat_text_view_append_message;
1296         iface->append_event = chat_text_view_append_event;
1297         iface->scroll = chat_text_view_scroll;
1298         iface->scroll_down = chat_text_view_scroll_down;
1299         iface->get_has_selection = chat_text_view_get_has_selection;
1300         iface->clear = chat_text_view_clear;
1301         iface->find_previous = chat_text_view_find_previous;
1302         iface->find_next = chat_text_view_find_next;
1303         iface->find_abilities = chat_text_view_find_abilities;
1304         iface->highlight = chat_text_view_highlight;
1305         iface->copy_clipboard = chat_text_view_copy_clipboard;
1306 }
1307
1308 EmpathyContact *
1309 empathy_chat_text_view_get_last_contact (EmpathyChatTextView *view)
1310 {
1311         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
1312
1313         g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view), NULL);
1314
1315         return priv->last_contact;
1316 }
1317
1318 time_t
1319 empathy_chat_text_view_get_last_timestamp (EmpathyChatTextView *view)
1320 {
1321         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
1322
1323         g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view), 0);
1324
1325         return priv->last_timestamp;
1326 }
1327
1328 void
1329 empathy_chat_text_view_set_only_if_date (EmpathyChatTextView *view,
1330                                          gboolean             only_if_date)
1331 {
1332         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
1333
1334         g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view));
1335
1336         if (only_if_date != priv->only_if_date) {
1337                 priv->only_if_date = only_if_date;
1338                 g_object_notify (G_OBJECT (view), "only-if-date");
1339         }
1340 }
1341
1342 static void
1343 chat_text_view_replace_link (const gchar *text,
1344                              gssize len,
1345                              gpointer match_data,
1346                              gpointer user_data)
1347 {
1348         GtkTextBuffer *buffer = GTK_TEXT_BUFFER (user_data);
1349         GtkTextIter iter;
1350
1351         gtk_text_buffer_get_end_iter (buffer, &iter);
1352         gtk_text_buffer_insert_with_tags_by_name (buffer, &iter,
1353                                                   text, len,
1354                                                   EMPATHY_CHAT_TEXT_VIEW_TAG_LINK,
1355                                                   NULL);
1356 }
1357
1358 static void
1359 chat_text_view_replace_smiley (const gchar *text,
1360                                gssize len,
1361                                gpointer match_data,
1362                                gpointer user_data)
1363 {
1364         EmpathySmileyHit *hit = match_data;
1365         GtkTextBuffer *buffer = GTK_TEXT_BUFFER (user_data);
1366         GtkTextIter iter;
1367
1368         gtk_text_buffer_get_end_iter (buffer, &iter);
1369         gtk_text_buffer_insert_pixbuf (buffer, &iter, hit->pixbuf);
1370 }
1371
1372 static void
1373 chat_text_view_replace_verbatim (const gchar *text,
1374                                  gssize len,
1375                                  gpointer match_data,
1376                                  gpointer user_data)
1377 {
1378         GtkTextBuffer *buffer = GTK_TEXT_BUFFER (user_data);
1379         GtkTextIter iter;
1380
1381         gtk_text_buffer_get_end_iter (buffer, &iter);
1382         gtk_text_buffer_insert (buffer, &iter, text, len);
1383 }
1384
1385 static EmpathyStringParser string_parsers[] = {
1386         {empathy_string_match_link, chat_text_view_replace_link},
1387         {empathy_string_match_all, chat_text_view_replace_verbatim},
1388         {NULL, NULL}
1389 };
1390
1391 static EmpathyStringParser string_parsers_with_smiley[] = {
1392         {empathy_string_match_link, chat_text_view_replace_link},
1393         {empathy_string_match_smiley, chat_text_view_replace_smiley},
1394         {empathy_string_match_all, chat_text_view_replace_verbatim},
1395         {NULL, NULL}
1396 };
1397
1398 void
1399 empathy_chat_text_view_append_body (EmpathyChatTextView *view,
1400                                     const gchar         *body,
1401                                     const gchar         *tag)
1402 {
1403         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
1404         EmpathyStringParser     *parsers;
1405         gboolean                 use_smileys;
1406         GtkTextIter              start_iter;
1407         GtkTextIter              iter;
1408         GtkTextMark             *mark;
1409         GSettings               *gsettings_chat;
1410
1411         /* Check if we have to parse smileys */
1412         gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1413         use_smileys = g_settings_get_boolean (gsettings_chat,
1414                         EMPATHY_PREFS_CHAT_SHOW_SMILEYS);
1415
1416         if (use_smileys)
1417                 parsers = string_parsers_with_smiley;
1418         else
1419                 parsers = string_parsers;
1420
1421         /* Create a mark at the place we'll start inserting */
1422         gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
1423         mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
1424
1425         /* Parse text for links/smileys and insert in the buffer */
1426         empathy_string_parser_substr (body, -1, parsers, priv->buffer);
1427
1428         /* Insert a newline after the text inserted */
1429         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1430         gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
1431
1432         /* Apply the style to the inserted text. */
1433         gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
1434         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1435         gtk_text_buffer_apply_tag_by_name (priv->buffer, tag,
1436                                            &start_iter,
1437                                            &iter);
1438
1439         gtk_text_buffer_delete_mark (priv->buffer, mark);
1440
1441         g_object_unref (gsettings_chat);
1442 }
1443
1444 void
1445 empathy_chat_text_view_append_spacing (EmpathyChatTextView *view)
1446 {
1447         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
1448         GtkTextIter              iter;
1449
1450         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1451         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1452                                                   &iter,
1453                                                   "\n",
1454                                                   -1,
1455                                                   EMPATHY_CHAT_TEXT_VIEW_TAG_CUT,
1456                                                   EMPATHY_CHAT_TEXT_VIEW_TAG_SPACING,
1457                                                   NULL);
1458 }
1459
1460 GtkTextTag *
1461 empathy_chat_text_view_tag_set (EmpathyChatTextView *view,
1462                                 const gchar         *tag_name,
1463                                 const gchar         *first_property_name,
1464                                 ...)
1465 {
1466         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
1467         GtkTextTag              *tag;
1468         GtkTextTagTable         *table;
1469         va_list                  list;
1470
1471         g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view), NULL);
1472         g_return_val_if_fail (tag_name != NULL, NULL);
1473
1474         table = gtk_text_buffer_get_tag_table (priv->buffer);
1475         tag = gtk_text_tag_table_lookup (table, tag_name);
1476
1477         if (tag && first_property_name) {
1478                 va_start (list, first_property_name);
1479                 g_object_set_valist (G_OBJECT (tag), first_property_name, list);
1480                 va_end (list);
1481         }
1482
1483         return tag;
1484 }
1485