]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat-text-view.c
stop using gdk_window_get_pointer()
[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 (5 * G_TIME_SPAN_MINUTE)
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         gint64                last_timestamp;
69         gboolean              allow_scrolling;
70         guint                 notify_system_fonts_id;
71         GSettings            *gsettings_desktop;
72         GSettings            *gsettings_chat;
73         EmpathySmileyManager *smiley_manager;
74         gboolean              only_if_date;
75 } EmpathyChatTextViewPriv;
76
77 static void chat_text_view_iface_init (EmpathyChatViewIface *iface);
78
79 static void chat_text_view_copy_clipboard (EmpathyChatView *view);
80
81 G_DEFINE_TYPE_WITH_CODE (EmpathyChatTextView, empathy_chat_text_view,
82                          GTK_TYPE_TEXT_VIEW,
83                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
84                                                 chat_text_view_iface_init));
85
86 enum {
87         PROP_0,
88         PROP_LAST_CONTACT,
89         PROP_ONLY_IF_DATE
90 };
91
92 static gboolean
93 chat_text_view_url_event_cb (GtkTextTag          *tag,
94                              GObject             *object,
95                              GdkEvent            *event,
96                              GtkTextIter         *iter,
97                              EmpathyChatTextView *view)
98 {
99         EmpathyChatTextViewPriv *priv;
100         GtkTextIter              start, end;
101         gchar                   *str;
102
103         priv = GET_PRIV (view);
104
105         /* If the link is being selected, don't do anything. */
106         gtk_text_buffer_get_selection_bounds (priv->buffer, &start, &end);
107         if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
108                 return FALSE;
109         }
110
111         if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
112                 start = end = *iter;
113
114                 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
115                     gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
116                             str = gtk_text_buffer_get_text (priv->buffer,
117                                                             &start,
118                                                             &end,
119                                                             FALSE);
120
121                             empathy_url_show (GTK_WIDGET (view), str);
122                             g_free (str);
123                     }
124         }
125
126         return FALSE;
127 }
128
129 static gboolean
130 chat_text_view_event_cb (EmpathyChatTextView *view,
131                          GdkEventMotion      *event,
132                          GtkTextTag          *tag)
133 {
134         static GdkCursor  *hand = NULL;
135         static GdkCursor  *beam = NULL;
136         GtkTextWindowType  type;
137         GtkTextIter        iter;
138         GdkWindow         *win;
139         gint               x, y, buf_x, buf_y;
140
141         type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
142                                               event->window);
143
144         if (type != GTK_TEXT_WINDOW_TEXT) {
145                 return FALSE;
146         }
147
148         /* Get where the pointer really is. */
149         win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
150         if (!win) {
151                 return FALSE;
152         }
153
154         gdk_window_get_device_position (win, event->device, &x, &y, NULL);
155
156         /* Get the iter where the cursor is at */
157         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
158                                                x, y,
159                                                &buf_x, &buf_y);
160
161         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
162                                             &iter,
163                                             buf_x, buf_y);
164
165         if (gtk_text_iter_has_tag (&iter, tag)) {
166                 if (!hand) {
167                         hand = gdk_cursor_new (GDK_HAND2);
168                         beam = gdk_cursor_new (GDK_XTERM);
169                 }
170                 gdk_window_set_cursor (win, hand);
171         } else {
172                 if (!beam) {
173                         beam = gdk_cursor_new (GDK_XTERM);
174                 }
175                 gdk_window_set_cursor (win, beam);
176         }
177
178         return FALSE;
179 }
180
181 static void
182 chat_text_view_create_tags (EmpathyChatTextView *view)
183 {
184         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
185         GtkTextTag              *tag;
186
187         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_CUT, NULL);
188         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_HIGHLIGHT, NULL);
189         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_SPACING, NULL);
190         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_TIME, NULL);
191         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_ACTION, NULL);
192         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_BODY, NULL);
193         gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_EVENT, NULL);
194
195         tag = gtk_text_buffer_create_tag (priv->buffer, EMPATHY_CHAT_TEXT_VIEW_TAG_LINK, NULL);
196         g_signal_connect (tag, "event",
197                           G_CALLBACK (chat_text_view_url_event_cb),
198                           view);
199
200         g_signal_connect (view, "motion-notify-event",
201                           G_CALLBACK (chat_text_view_event_cb),
202                           tag);
203 }
204
205 static void
206 chat_text_view_system_font_update (EmpathyChatTextView *view)
207 {
208         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
209         PangoFontDescription *font_description = NULL;
210         gchar                *font_name;
211
212         font_name = g_settings_get_string (priv->gsettings_desktop,
213                         EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
214
215         if (font_name != NULL) {
216                 font_description = pango_font_description_from_string (font_name);
217                 g_free (font_name);
218         } else {
219                 font_description = NULL;
220         }
221
222         gtk_widget_override_font (GTK_WIDGET (view), font_description);
223
224         if (font_description) {
225                 pango_font_description_free (font_description);
226         }
227 }
228
229 static void
230 chat_text_view_notify_system_font_cb (GSettings *gsettings,
231                                       const gchar *key,
232                                       EmpathyChatTextView *self)
233 {
234         chat_text_view_system_font_update (self);
235 }
236
237 static void
238 chat_text_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
239 {
240         empathy_url_show (GTK_WIDGET (menuitem), url);
241 }
242
243 static void
244 chat_text_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
245 {
246         GtkClipboard *clipboard;
247
248         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
249         gtk_clipboard_set_text (clipboard, url, -1);
250
251         clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
252         gtk_clipboard_set_text (clipboard, url, -1);
253 }
254
255 static void
256 chat_text_view_populate_popup (EmpathyChatTextView *view,
257                                GtkMenu        *menu,
258                                gpointer        user_data)
259 {
260         EmpathyChatTextViewPriv *priv;
261         GtkTextTagTable    *table;
262         GtkTextTag         *tag;
263         gint                x, y;
264         GtkTextIter         iter, start, end;
265         GtkWidget          *item;
266         gchar              *str = NULL;
267
268         priv = GET_PRIV (view);
269
270         /* Clear menu item */
271         if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
272                 item = gtk_separator_menu_item_new ();
273                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
274                 gtk_widget_show (item);
275
276                 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
277                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
278                 gtk_widget_show (item);
279
280                 g_signal_connect_swapped (item, "activate",
281                                           G_CALLBACK (empathy_chat_view_clear),
282                                           view);
283         }
284
285         /* Link context menu items */
286         table = gtk_text_buffer_get_tag_table (priv->buffer);
287         tag = gtk_text_tag_table_lookup (table, EMPATHY_CHAT_TEXT_VIEW_TAG_LINK);
288
289         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
290
291         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
292                                                GTK_TEXT_WINDOW_WIDGET,
293                                                x, y,
294                                                &x, &y);
295
296         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
297
298         start = end = iter;
299
300         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
301             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
302                     str = gtk_text_buffer_get_text (priv->buffer,
303                                                     &start, &end, FALSE);
304             }
305
306         if (EMP_STR_EMPTY (str)) {
307                 g_free (str);
308                 return;
309         }
310
311         /* NOTE: Set data just to get the string freed when not needed. */
312         g_object_set_data_full (G_OBJECT (menu),
313                                 "url", str,
314                                 (GDestroyNotify) g_free);
315
316         item = gtk_separator_menu_item_new ();
317         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
318         gtk_widget_show (item);
319
320         item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
321         g_signal_connect (item, "activate",
322                           G_CALLBACK (chat_text_view_copy_address_cb),
323                           str);
324         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
325         gtk_widget_show (item);
326
327         item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
328         g_signal_connect (item, "activate",
329                           G_CALLBACK (chat_text_view_open_address_cb),
330                           str);
331         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
332         gtk_widget_show (item);
333 }
334
335 static gboolean
336 chat_text_view_is_scrolled_down (EmpathyChatTextView *view)
337 {
338         GtkAdjustment *vadj;
339         gdouble value;
340         gdouble upper;
341         gdouble page_size;
342
343         vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (view));
344         value = gtk_adjustment_get_value (vadj);
345         upper = gtk_adjustment_get_upper (vadj);
346         page_size = gtk_adjustment_get_page_size (vadj);
347
348         if (value < upper - page_size) {
349                 return FALSE;
350         }
351
352         return TRUE;
353 }
354
355 static void
356 chat_text_view_maybe_trim_buffer (EmpathyChatTextView *view)
357 {
358         EmpathyChatTextViewPriv *priv;
359         GtkTextIter         top, bottom;
360         gint                line;
361         gint                remove_;
362         GtkTextTagTable    *table;
363         GtkTextTag         *tag;
364
365         priv = GET_PRIV (view);
366
367         gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
368         line = gtk_text_iter_get_line (&bottom);
369         if (line < MAX_LINES) {
370                 return;
371         }
372
373         remove_ = line - MAX_LINES;
374         gtk_text_buffer_get_start_iter (priv->buffer, &top);
375
376         bottom = top;
377         if (!gtk_text_iter_forward_lines (&bottom, remove_)) {
378                 return;
379         }
380
381         /* Track backwords to a place where we can safely cut, we don't do it in
382           * the middle of a tag.
383           */
384         table = gtk_text_buffer_get_tag_table (priv->buffer);
385         tag = gtk_text_tag_table_lookup (table, EMPATHY_CHAT_TEXT_VIEW_TAG_CUT);
386         if (!tag) {
387                 return;
388         }
389
390         if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
391                 return;
392         }
393
394         if (!gtk_text_iter_equal (&top, &bottom)) {
395                 gtk_text_buffer_delete (priv->buffer, &top, &bottom);
396         }
397 }
398
399 static void
400 chat_text_view_append_timestamp (EmpathyChatTextView *view,
401                                  gint64               timestamp,
402                                  gboolean             show_date)
403 {
404         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
405         GtkTextIter              iter;
406         gchar                   *tmp;
407         GString                 *str;
408
409         str = g_string_new ("- ");
410
411         /* Append date if needed */
412         if (show_date) {
413                 /* Translators: timestamp displayed between conversations in
414                  * chat windows (strftime format string) */
415                 tmp = empathy_time_to_string_utc (timestamp, _("%A %B %d %Y"));
416                 g_string_append (str, tmp);
417                 g_string_append (str, ", ");
418                 g_free (tmp);
419         }
420
421         /* Append time */
422         tmp = empathy_time_to_string_local (timestamp, EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
423         g_string_append (str, tmp);
424         g_free (tmp);
425
426         g_string_append (str, " -\n");
427
428         /* Insert the string in the buffer */
429         empathy_chat_text_view_append_spacing (view);
430         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
431         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
432                                                   &iter,
433                                                   str->str, -1,
434                                                   EMPATHY_CHAT_TEXT_VIEW_TAG_TIME,
435                                                   NULL);
436
437         g_string_free (str, TRUE);
438 }
439
440 static void
441 chat_text_maybe_append_date_and_time (EmpathyChatTextView *view,
442                                       gint64               timestamp)
443 {
444         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
445         GDateTime               *date, *last_date;
446         gboolean                 append_date = FALSE;
447         gboolean                 append_time = FALSE;
448         GTimeSpan                delta;
449
450         /* Get the date from last message */
451         last_date = g_date_time_new_from_unix_utc (priv->last_timestamp);
452
453         /* Get the date of the message we are appending */
454         date = g_date_time_new_from_unix_utc (timestamp);
455
456         delta = g_date_time_difference (date, last_date);
457         /* If last message was from another day we append date and time */
458         if (delta >= G_TIME_SPAN_DAY) {
459                 append_date = TRUE;
460                 append_time = TRUE;
461         }
462
463         g_date_time_unref (last_date);
464         g_date_time_unref (date);
465
466         /* If last message is 'old' append the time */
467         if (delta >= TIMESTAMP_INTERVAL) {
468                 append_time = TRUE;
469         }
470
471         if (append_date || (!priv->only_if_date && append_time)) {
472                 chat_text_view_append_timestamp (view, timestamp, append_date);
473         }
474 }
475
476 static void
477 chat_text_view_size_allocate (GtkWidget     *widget,
478                               GtkAllocation *alloc)
479 {
480         gboolean down;
481
482         down = chat_text_view_is_scrolled_down (EMPATHY_CHAT_TEXT_VIEW (widget));
483
484         GTK_WIDGET_CLASS (empathy_chat_text_view_parent_class)->size_allocate (widget, alloc);
485
486         if (down) {
487                 GtkAdjustment *adj;
488
489                 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
490                 gtk_adjustment_set_value (adj,
491                                           gtk_adjustment_get_upper (adj) -
492                                           gtk_adjustment_get_page_size (adj));
493         }
494 }
495
496 static gboolean
497 chat_text_view_drag_motion (GtkWidget      *widget,
498                             GdkDragContext *context,
499                             gint            x,
500                             gint            y,
501                             guint           time_)
502 {
503         /* Don't handle drag motion, since we don't want the view to scroll as
504          * the result of dragging something across it. */
505
506         return FALSE;
507 }
508
509 static void
510 chat_text_view_get_property (GObject    *object,
511                              guint       param_id,
512                              GValue     *value,
513                              GParamSpec *pspec)
514 {
515         EmpathyChatTextViewPriv *priv = GET_PRIV (object);
516
517         switch (param_id) {
518         case PROP_LAST_CONTACT:
519                 g_value_set_object (value, priv->last_contact);
520                 break;
521         case PROP_ONLY_IF_DATE:
522                 g_value_set_boolean (value, priv->only_if_date);
523                 break;
524         default:
525                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
526                 break;
527         };
528 }
529
530 static void
531 chat_text_view_set_property (GObject      *object,
532                              guint         param_id,
533                              const GValue *value,
534                              GParamSpec   *pspec)
535 {
536         EmpathyChatTextViewPriv *priv = GET_PRIV (object);
537
538         switch (param_id) {
539         case PROP_ONLY_IF_DATE:
540                 priv->only_if_date = g_value_get_boolean (value);
541                 break;
542         default:
543                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
544                 break;
545         };
546 }
547
548 static void
549 chat_text_view_finalize (GObject *object)
550 {
551         EmpathyChatTextView     *view;
552         EmpathyChatTextViewPriv *priv;
553
554         view = EMPATHY_CHAT_TEXT_VIEW (object);
555         priv = GET_PRIV (view);
556
557         DEBUG ("%p", object);
558
559         g_object_unref (priv->gsettings_chat);
560         g_object_unref (priv->gsettings_desktop);
561
562         if (priv->last_contact) {
563                 g_object_unref (priv->last_contact);
564         }
565         if (priv->scroll_time) {
566                 g_timer_destroy (priv->scroll_time);
567         }
568         if (priv->scroll_timeout) {
569                 g_source_remove (priv->scroll_timeout);
570         }
571         g_object_unref (priv->smiley_manager);
572
573         G_OBJECT_CLASS (empathy_chat_text_view_parent_class)->finalize (object);
574 }
575
576 static void
577 text_view_copy_clipboard (GtkTextView *text_view)
578 {
579         chat_text_view_copy_clipboard (EMPATHY_CHAT_VIEW (text_view));
580 }
581
582 static void
583 empathy_chat_text_view_class_init (EmpathyChatTextViewClass *klass)
584 {
585         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
586         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
587         GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
588
589         object_class->finalize = chat_text_view_finalize;
590         object_class->get_property = chat_text_view_get_property;
591         object_class->set_property = chat_text_view_set_property;
592
593         widget_class->size_allocate = chat_text_view_size_allocate;
594         widget_class->drag_motion = chat_text_view_drag_motion;
595
596         text_view_class->copy_clipboard = text_view_copy_clipboard;
597
598         g_object_class_install_property (object_class,
599                                          PROP_LAST_CONTACT,
600                                          g_param_spec_object ("last-contact",
601                                                               "Last contact",
602                                                               "The sender of the last received message",
603                                                               EMPATHY_TYPE_CONTACT,
604                                                               G_PARAM_READABLE));
605         g_object_class_install_property (object_class,
606                                          PROP_ONLY_IF_DATE,
607                                          g_param_spec_boolean ("only-if-date",
608                                                               "Only if date",
609                                                               "Display timestamp only if the date changes",
610                                                               FALSE,
611                                                               G_PARAM_READWRITE));
612
613
614         g_type_class_add_private (object_class, sizeof (EmpathyChatTextViewPriv));
615 }
616
617 static void
618 empathy_chat_text_view_init (EmpathyChatTextView *view)
619 {
620         EmpathyChatTextViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
621                 EMPATHY_TYPE_CHAT_TEXT_VIEW, EmpathyChatTextViewPriv);
622
623         view->priv = priv;
624         priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
625         priv->last_timestamp = 0;
626         priv->allow_scrolling = TRUE;
627         priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
628
629         g_object_set (view,
630                       "wrap-mode", GTK_WRAP_WORD_CHAR,
631                       "editable", FALSE,
632                       "cursor-visible", FALSE,
633                       NULL);
634
635         priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
636
637         priv->gsettings_desktop = g_settings_new (
638                           EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
639         g_signal_connect (priv->gsettings_desktop,
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         gint64                   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 gint64
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
1410         /* Check if we have to parse smileys */
1411         use_smileys = g_settings_get_boolean (priv->gsettings_chat,
1412                         EMPATHY_PREFS_CHAT_SHOW_SMILEYS);
1413
1414         if (use_smileys)
1415                 parsers = string_parsers_with_smiley;
1416         else
1417                 parsers = string_parsers;
1418
1419         /* Create a mark at the place we'll start inserting */
1420         gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
1421         mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
1422
1423         /* Parse text for links/smileys and insert in the buffer */
1424         empathy_string_parser_substr (body, -1, parsers, priv->buffer);
1425
1426         /* Insert a newline after the text inserted */
1427         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1428         gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
1429
1430         /* Apply the style to the inserted text. */
1431         gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
1432         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1433         gtk_text_buffer_apply_tag_by_name (priv->buffer, tag,
1434                                            &start_iter,
1435                                            &iter);
1436
1437         gtk_text_buffer_delete_mark (priv->buffer, mark);
1438 }
1439
1440 void
1441 empathy_chat_text_view_append_spacing (EmpathyChatTextView *view)
1442 {
1443         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
1444         GtkTextIter              iter;
1445
1446         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1447         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1448                                                   &iter,
1449                                                   "\n",
1450                                                   -1,
1451                                                   EMPATHY_CHAT_TEXT_VIEW_TAG_CUT,
1452                                                   EMPATHY_CHAT_TEXT_VIEW_TAG_SPACING,
1453                                                   NULL);
1454 }
1455
1456 GtkTextTag *
1457 empathy_chat_text_view_tag_set (EmpathyChatTextView *view,
1458                                 const gchar         *tag_name,
1459                                 const gchar         *first_property_name,
1460                                 ...)
1461 {
1462         EmpathyChatTextViewPriv *priv = GET_PRIV (view);
1463         GtkTextTag              *tag;
1464         GtkTextTagTable         *table;
1465         va_list                  list;
1466
1467         g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view), NULL);
1468         g_return_val_if_fail (tag_name != NULL, NULL);
1469
1470         table = gtk_text_buffer_get_tag_table (priv->buffer);
1471         tag = gtk_text_tag_table_lookup (table, tag_name);
1472
1473         if (tag && first_property_name) {
1474                 va_start (list, first_property_name);
1475                 g_object_set_valist (G_OBJECT (tag), first_property_name, list);
1476                 va_end (list);
1477         }
1478
1479         return tag;
1480 }
1481