]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-ui-utils.c
Make possible to define different replace function for parsers
[empathy.git] / libempathy-gtk / empathy-ui-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2002-2007 Imendio AB
4  * Copyright (C) 2007-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  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
26  *
27  *          Part of this file is copied from GtkSourceView (gtksourceiter.c):
28  *          Paolo Maggi
29  *          Jeroen Zwartepoorte
30  */
31
32 #include <config.h>
33
34 #include <string.h>
35 #include <X11/Xatom.h>
36 #include <gdk/gdkx.h>
37 #include <glib/gi18n-lib.h>
38 #include <gtk/gtk.h>
39 #include <gio/gio.h>
40
41 #include "empathy-ui-utils.h"
42 #include "empathy-images.h"
43 #include "empathy-smiley-manager.h"
44 #include "empathy-conf.h"
45
46 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
47 #include <libempathy/empathy-debug.h>
48 #include <libempathy/empathy-utils.h>
49 #include <libempathy/empathy-dispatcher.h>
50 #include <libempathy/empathy-idle.h>
51 #include <libempathy/empathy-ft-factory.h>
52
53 #define SCHEMES   "([a-zA-Z\\+]+)"
54 #define BODY_CHAR "[^\\ \\n\"\']"
55 #define BODY      "("BODY_CHAR"+)"
56 #define END_BODY  "("BODY_CHAR"*[^,;\?><()\\ \"\'\\.\\n])"
57 #define URI_REGEX "("SCHEMES"://"END_BODY")" \
58                   "|((mailto:)?"BODY"@"BODY"\\."END_BODY")"\
59                   "|((www|ftp)\\."END_BODY")"
60
61 void
62 empathy_gtk_init (void)
63 {
64         static gboolean initialized = FALSE;
65
66         if (initialized)
67                 return;
68
69         empathy_init ();
70         gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
71                                            PKGDATADIR G_DIR_SEPARATOR_S "icons");
72
73         initialized = TRUE;
74 }
75
76 GRegex *
77 empathy_uri_regex_dup_singleton (void)
78 {
79         static GRegex *uri_regex = NULL;
80
81         /* We intentionally leak the regex so it's not recomputed */
82         if (!uri_regex) {
83                 uri_regex = g_regex_new (URI_REGEX, 0, 0, NULL);
84         }
85
86         return g_regex_ref (uri_regex);
87 }
88
89 static GtkBuilder *
90 builder_get_file_valist (const gchar *filename,
91                          const gchar *first_object,
92                          va_list      args)
93 {
94         GtkBuilder  *gui;
95         const gchar *name;
96         GObject    **object_ptr;
97         GError      *error = NULL;
98
99         DEBUG ("Loading file %s", filename);
100
101         gui = gtk_builder_new ();
102         if (!gtk_builder_add_from_file (gui, filename, &error)) {
103                 g_critical ("GtkBuilder Error (%s): %s",
104                                 filename, error->message);
105                 g_clear_error (&error);
106                 g_object_unref (gui);
107
108                 /* we need to iterate and set all of the pointers to NULL */
109                 for (name = first_object; name;
110                      name = va_arg (args, const gchar *)) {
111                         object_ptr = va_arg (args, GObject**);
112
113                         *object_ptr = NULL;
114                 }
115
116                 return NULL;
117         }
118
119         for (name = first_object; name; name = va_arg (args, const gchar *)) {
120                 object_ptr = va_arg (args, GObject**);
121
122                 *object_ptr = gtk_builder_get_object (gui, name);
123
124                 if (!*object_ptr) {
125                         g_warning ("File is missing object '%s'.", name);
126                         continue;
127                 }
128         }
129
130         return gui;
131 }
132
133 GtkBuilder *
134 empathy_builder_get_file (const gchar *filename,
135                           const gchar *first_object,
136                           ...)
137 {
138         GtkBuilder *gui;
139         va_list     args;
140
141         va_start (args, first_object);
142         gui = builder_get_file_valist (filename, first_object, args);
143         va_end (args);
144
145         return gui;
146 }
147
148 void
149 empathy_builder_connect (GtkBuilder *gui,
150                          gpointer    user_data,
151                          gchar      *first_object,
152                          ...)
153 {
154         va_list      args;
155         const gchar *name;
156         const gchar *sig;
157         GObject     *object;
158         GCallback    callback;
159
160         va_start (args, first_object);
161         for (name = first_object; name; name = va_arg (args, const gchar *)) {
162                 sig = va_arg (args, const gchar *);
163                 callback = va_arg (args, GCallback);
164
165                 object = gtk_builder_get_object (gui, name);
166                 if (!object) {
167                         g_warning ("File is missing object '%s'.", name);
168                         continue;
169                 }
170
171                 g_signal_connect (object, sig, callback, user_data);
172         }
173
174         va_end (args);
175 }
176
177 GtkWidget *
178 empathy_builder_unref_and_keep_widget (GtkBuilder *gui,
179                                        GtkWidget  *widget)
180 {
181         /* On construction gui sinks the initial reference to widget. When gui
182          * is finalized it will drop its ref to widget. We take our own ref to
183          * prevent widget being finalised. The widget is forced to have a
184          * floating reference, like when it was initially unowned so that it can
185          * be used like any other GtkWidget. */
186
187         g_object_ref (widget);
188         g_object_force_floating (G_OBJECT (widget));
189         g_object_unref (gui);
190
191         return widget;
192 }
193
194 const gchar *
195 empathy_icon_name_for_presence (TpConnectionPresenceType presence)
196 {
197         switch (presence) {
198         case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
199                 return EMPATHY_IMAGE_AVAILABLE;
200         case TP_CONNECTION_PRESENCE_TYPE_BUSY:
201                 return EMPATHY_IMAGE_BUSY;
202         case TP_CONNECTION_PRESENCE_TYPE_AWAY:
203                 return EMPATHY_IMAGE_AWAY;
204         case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
205                 return EMPATHY_IMAGE_EXT_AWAY;
206         case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
207                 return EMPATHY_IMAGE_HIDDEN;
208         case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
209         case TP_CONNECTION_PRESENCE_TYPE_ERROR:
210         case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
211                 return EMPATHY_IMAGE_OFFLINE;
212         case TP_CONNECTION_PRESENCE_TYPE_UNSET:
213                 return NULL;
214         }
215
216         return NULL;
217 }
218
219 const gchar *
220 empathy_icon_name_for_contact (EmpathyContact *contact)
221 {
222         TpConnectionPresenceType presence;
223
224         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact),
225                               EMPATHY_IMAGE_OFFLINE);
226
227         presence = empathy_contact_get_presence (contact);
228         return empathy_icon_name_for_presence (presence);
229 }
230
231 GdkPixbuf *
232 empathy_pixbuf_from_data (gchar *data,
233                           gsize  data_size)
234 {
235         return empathy_pixbuf_from_data_and_mime (data, data_size, NULL);
236 }
237
238 GdkPixbuf *
239 empathy_pixbuf_from_data_and_mime (gchar  *data,
240                                    gsize   data_size,
241                                    gchar **mime_type)
242 {
243         GdkPixbufLoader *loader;
244         GdkPixbufFormat *format;
245         GdkPixbuf       *pixbuf = NULL;
246         gchar          **mime_types;
247         GError          *error = NULL;
248
249         if (!data) {
250                 return NULL;
251         }
252
253         loader = gdk_pixbuf_loader_new ();
254         if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size, &error)) {
255                 DEBUG ("Failed to write to pixbuf loader: %s",
256                         error ? error->message : "No error given");
257                 goto out;
258         }
259         if (!gdk_pixbuf_loader_close (loader, &error)) {
260                 DEBUG ("Failed to close pixbuf loader: %s",
261                         error ? error->message : "No error given");
262                 goto out;
263         }
264
265         pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
266         if (pixbuf) {
267                 g_object_ref (pixbuf);
268
269                 if (mime_type != NULL) {
270                         format = gdk_pixbuf_loader_get_format (loader);
271                         mime_types = gdk_pixbuf_format_get_mime_types (format);
272
273                         *mime_type = g_strdup (*mime_types);
274                         if (mime_types[1] != NULL) {
275                                 DEBUG ("Loader supports more than one mime "
276                                         "type! Picking the first one, %s",
277                                         *mime_type);
278                         }
279                         g_strfreev (mime_types);
280                 }
281         }
282
283 out:
284         g_clear_error (&error);
285         g_object_unref (loader);
286
287         return pixbuf;
288 }
289
290 struct SizeData {
291         gint     width;
292         gint     height;
293         gboolean preserve_aspect_ratio;
294 };
295
296 static void
297 pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
298                                      int              width,
299                                      int              height,
300                                      struct SizeData *data)
301 {
302         g_return_if_fail (width > 0 && height > 0);
303
304         if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0)) {
305                 if (width < data->width && height < data->height) {
306                         width = width;
307                         height = height;
308                 }
309
310                 if (data->width < 0) {
311                         width = width * (double) data->height / (gdouble) height;
312                         height = data->height;
313                 } else if (data->height < 0) {
314                         height = height * (double) data->width / (double) width;
315                         width = data->width;
316                 } else if ((double) height * (double) data->width >
317                            (double) width * (double) data->height) {
318                         width = 0.5 + (double) width * (double) data->height / (double) height;
319                         height = data->height;
320                 } else {
321                         height = 0.5 + (double) height * (double) data->width / (double) width;
322                         width = data->width;
323                 }
324         } else {
325                 if (data->width > 0) {
326                         width = data->width;
327                 }
328
329                 if (data->height > 0) {
330                         height = data->height;
331                 }
332         }
333
334         gdk_pixbuf_loader_set_size (loader, width, height);
335 }
336
337 static void
338 empathy_avatar_pixbuf_roundify (GdkPixbuf *pixbuf)
339 {
340         gint width, height, rowstride;
341         guchar *pixels;
342
343         width = gdk_pixbuf_get_width (pixbuf);
344         height = gdk_pixbuf_get_height (pixbuf);
345         rowstride = gdk_pixbuf_get_rowstride (pixbuf);
346         pixels = gdk_pixbuf_get_pixels (pixbuf);
347
348         if (width < 6 || height < 6) {
349                 return;
350         }
351
352         /* Top left */
353         pixels[3] = 0;
354         pixels[7] = 0x80;
355         pixels[11] = 0xC0;
356         pixels[rowstride + 3] = 0x80;
357         pixels[rowstride * 2 + 3] = 0xC0;
358
359         /* Top right */
360         pixels[width * 4 - 1] = 0;
361         pixels[width * 4 - 5] = 0x80;
362         pixels[width * 4 - 9] = 0xC0;
363         pixels[rowstride + (width * 4) - 1] = 0x80;
364         pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
365
366         /* Bottom left */
367         pixels[(height - 1) * rowstride + 3] = 0;
368         pixels[(height - 1) * rowstride + 7] = 0x80;
369         pixels[(height - 1) * rowstride + 11] = 0xC0;
370         pixels[(height - 2) * rowstride + 3] = 0x80;
371         pixels[(height - 3) * rowstride + 3] = 0xC0;
372
373         /* Bottom right */
374         pixels[height * rowstride - 1] = 0;
375         pixels[(height - 1) * rowstride - 1] = 0x80;
376         pixels[(height - 2) * rowstride - 1] = 0xC0;
377         pixels[height * rowstride - 5] = 0x80;
378         pixels[height * rowstride - 9] = 0xC0;
379 }
380
381 static gboolean
382 empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
383 {
384         gint width, height, rowstride, i;
385         guchar *pixels;
386         guchar *row;
387
388         width = gdk_pixbuf_get_width (pixbuf);
389         height = gdk_pixbuf_get_height (pixbuf);
390         rowstride = gdk_pixbuf_get_rowstride (pixbuf);
391         pixels = gdk_pixbuf_get_pixels (pixbuf);
392
393         row = pixels;
394         for (i = 3; i < rowstride; i+=4) {
395                 if (row[i] < 0xfe) {
396                         return FALSE;
397                 }
398         }
399
400         for (i = 1; i < height - 1; i++) {
401                 row = pixels + (i*rowstride);
402                 if (row[3] < 0xfe || row[rowstride-1] < 0xfe) {
403                         return FALSE;
404                 }
405         }
406
407         row = pixels + ((height-1) * rowstride);
408         for (i = 3; i < rowstride; i+=4) {
409                 if (row[i] < 0xfe) {
410                         return FALSE;
411                 }
412         }
413
414         return TRUE;
415 }
416
417 GdkPixbuf *
418 empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
419                                   gint          width,
420                                   gint          height)
421 {
422         GdkPixbuf        *pixbuf;
423         GdkPixbufLoader  *loader;
424         struct SizeData   data;
425         GError           *error = NULL;
426
427         if (!avatar) {
428                 return NULL;
429         }
430
431         data.width = width;
432         data.height = height;
433         data.preserve_aspect_ratio = TRUE;
434
435         loader = gdk_pixbuf_loader_new ();
436
437         g_signal_connect (loader, "size-prepared",
438                           G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
439                           &data);
440
441         if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
442                 g_warning ("Couldn't write avatar image:%p with "
443                            "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
444                            avatar->data, avatar->len, error->message);
445                 g_error_free (error);
446                 return NULL;
447         }
448
449         gdk_pixbuf_loader_close (loader, NULL);
450
451         pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
452         if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
453                 GdkPixbuf *rounded_pixbuf;
454
455                 rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
456                                                  gdk_pixbuf_get_width (pixbuf),
457                                                  gdk_pixbuf_get_height (pixbuf));
458                 gdk_pixbuf_copy_area (pixbuf, 0, 0,
459                                       gdk_pixbuf_get_width (pixbuf),
460                                       gdk_pixbuf_get_height (pixbuf),
461                                       rounded_pixbuf,
462                                       0, 0);
463                 pixbuf = rounded_pixbuf;
464         } else {
465                 g_object_ref (pixbuf);
466         }
467
468         if (empathy_gdk_pixbuf_is_opaque (pixbuf)) {
469                 empathy_avatar_pixbuf_roundify (pixbuf);
470         }
471
472         g_object_unref (loader);
473
474         return pixbuf;
475 }
476
477 GdkPixbuf *
478 empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact *contact,
479                                           gint           width,
480                                           gint           height)
481 {
482         EmpathyAvatar *avatar;
483
484         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
485
486         avatar = empathy_contact_get_avatar (contact);
487
488         return empathy_pixbuf_from_avatar_scaled (avatar, width, height);
489 }
490
491 GdkPixbuf *
492 empathy_pixbuf_scale_down_if_necessary (GdkPixbuf *pixbuf, gint max_size)
493 {
494         gint      width, height;
495         gdouble   factor;
496
497         width = gdk_pixbuf_get_width (pixbuf);
498         height = gdk_pixbuf_get_height (pixbuf);
499
500         if (width > 0 && (width > max_size || height > max_size)) {
501                 factor = (gdouble) max_size / MAX (width, height);
502
503                 width = width * factor;
504                 height = height * factor;
505
506                 return gdk_pixbuf_scale_simple (pixbuf,
507                                                 width, height,
508                                                 GDK_INTERP_HYPER);
509         }
510
511         return g_object_ref (pixbuf);
512 }
513
514 GdkPixbuf *
515 empathy_pixbuf_from_icon_name_sized (const gchar *icon_name,
516                                      gint size)
517 {
518         GtkIconTheme *theme;
519         GdkPixbuf *pixbuf;
520         GError *error = NULL;
521
522         if (!icon_name) {
523                 return NULL;
524         }
525
526         theme = gtk_icon_theme_get_default ();
527
528         pixbuf = gtk_icon_theme_load_icon (theme,
529                                            icon_name,
530                                            size,
531                                            0,
532                                            &error);
533         if (error) {
534                 DEBUG ("Error loading icon: %s", error->message);
535                 g_clear_error (&error);
536         }
537
538         return pixbuf;
539 }
540
541 GdkPixbuf *
542 empathy_pixbuf_from_icon_name (const gchar *icon_name,
543                                GtkIconSize  icon_size)
544 {
545         gint  w, h;
546         gint  size = 48;
547
548         if (!icon_name) {
549                 return NULL;
550         }
551
552         if (gtk_icon_size_lookup (icon_size, &w, &h)) {
553                 size = (w + h) / 2;
554         }
555
556         return empathy_pixbuf_from_icon_name_sized (icon_name, size);
557 }
558
559 gchar *
560 empathy_filename_from_icon_name (const gchar *icon_name,
561                                  GtkIconSize  icon_size)
562 {
563         GtkIconTheme *icon_theme;
564         GtkIconInfo  *icon_info;
565         gint          w, h;
566         gint          size = 48;
567         gchar        *ret;
568
569         icon_theme = gtk_icon_theme_get_default ();
570
571         if (gtk_icon_size_lookup (icon_size, &w, &h)) {
572                 size = (w + h) / 2;
573         }
574
575         icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
576         ret = g_strdup (gtk_icon_info_get_filename (icon_info));
577         gtk_icon_info_free (icon_info);
578
579         return ret;
580 }
581
582 /* Stolen from GtkSourceView, hence the weird intendation. Please keep it like
583  * that to make it easier to apply changes from the original code.
584  */
585 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
586
587 /* this function acts like g_utf8_offset_to_pointer() except that if it finds a
588  * decomposable character it consumes the decomposition length from the given
589  * offset.  So it's useful when the offset was calculated for the normalized
590  * version of str, but we need a pointer to str itself. */
591 static const gchar *
592 pointer_from_offset_skipping_decomp (const gchar *str, gint offset)
593 {
594         gchar *casefold, *normal;
595         const gchar *p, *q;
596
597         p = str;
598         while (offset > 0)
599         {
600                 q = g_utf8_next_char (p);
601                 casefold = g_utf8_casefold (p, q - p);
602                 normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
603                 offset -= g_utf8_strlen (normal, -1);
604                 g_free (casefold);
605                 g_free (normal);
606                 p = q;
607         }
608         return p;
609 }
610
611 static const gchar *
612 g_utf8_strcasestr (const gchar *haystack, const gchar *needle)
613 {
614         gsize needle_len;
615         gsize haystack_len;
616         const gchar *ret = NULL;
617         gchar *p;
618         gchar *casefold;
619         gchar *caseless_haystack;
620         gint i;
621
622         g_return_val_if_fail (haystack != NULL, NULL);
623         g_return_val_if_fail (needle != NULL, NULL);
624
625         casefold = g_utf8_casefold (haystack, -1);
626         caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
627         g_free (casefold);
628
629         needle_len = g_utf8_strlen (needle, -1);
630         haystack_len = g_utf8_strlen (caseless_haystack, -1);
631
632         if (needle_len == 0)
633         {
634                 ret = (gchar *) haystack;
635                 goto finally_1;
636         }
637
638         if (haystack_len < needle_len)
639         {
640                 ret = NULL;
641                 goto finally_1;
642         }
643
644         p = (gchar *) caseless_haystack;
645         needle_len = strlen (needle);
646         i = 0;
647
648         while (*p)
649         {
650                 if ((strncmp (p, needle, needle_len) == 0))
651                 {
652                         ret = pointer_from_offset_skipping_decomp (haystack, i);
653                         goto finally_1;
654                 }
655
656                 p = g_utf8_next_char (p);
657                 i++;
658         }
659
660 finally_1:
661         g_free (caseless_haystack);
662
663         return ret;
664 }
665
666 static gboolean
667 g_utf8_caselessnmatch (const char *s1, const char *s2,
668                        gssize n1, gssize n2)
669 {
670         gchar *casefold;
671         gchar *normalized_s1;
672         gchar *normalized_s2;
673         gint len_s1;
674         gint len_s2;
675         gboolean ret = FALSE;
676
677         g_return_val_if_fail (s1 != NULL, FALSE);
678         g_return_val_if_fail (s2 != NULL, FALSE);
679         g_return_val_if_fail (n1 > 0, FALSE);
680         g_return_val_if_fail (n2 > 0, FALSE);
681
682         casefold = g_utf8_casefold (s1, n1);
683         normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
684         g_free (casefold);
685
686         casefold = g_utf8_casefold (s2, n2);
687         normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
688         g_free (casefold);
689
690         len_s1 = strlen (normalized_s1);
691         len_s2 = strlen (normalized_s2);
692
693         if (len_s1 < len_s2)
694                 goto finally_2;
695
696         ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
697
698 finally_2:
699         g_free (normalized_s1);
700         g_free (normalized_s2);
701
702         return ret;
703 }
704
705 static void
706 forward_chars_with_skipping (GtkTextIter *iter,
707                              gint         count,
708                              gboolean     skip_invisible,
709                              gboolean     skip_nontext,
710                              gboolean     skip_decomp)
711 {
712         gint i;
713
714         g_return_if_fail (count >= 0);
715
716         i = count;
717
718         while (i > 0)
719         {
720                 gboolean ignored = FALSE;
721
722                 /* minimal workaround to avoid the infinite loop of bug #168247.
723                  * It doesn't fix the problemjust the symptom...
724                  */
725                 if (gtk_text_iter_is_end (iter))
726                         return;
727
728                 if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR)
729                         ignored = TRUE;
730
731                 if (!ignored && skip_invisible &&
732                     /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE)
733                         ignored = TRUE;
734
735                 if (!ignored && skip_decomp)
736                 {
737                         /* being UTF8 correct sucks; this accounts for extra
738                            offsets coming from canonical decompositions of
739                            UTF8 characters (e.g. accented characters) which
740                            g_utf8_normalize () performs */
741                         gchar *normal;
742                         gchar buffer[6];
743                         gint buffer_len;
744
745                         buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer);
746                         normal = g_utf8_normalize (buffer, buffer_len, G_NORMALIZE_NFD);
747                         i -= (g_utf8_strlen (normal, -1) - 1);
748                         g_free (normal);
749                 }
750
751                 gtk_text_iter_forward_char (iter);
752
753                 if (!ignored)
754                         --i;
755         }
756 }
757
758 static gboolean
759 lines_match (const GtkTextIter *start,
760              const gchar      **lines,
761              gboolean           visible_only,
762              gboolean           slice,
763              GtkTextIter       *match_start,
764              GtkTextIter       *match_end)
765 {
766         GtkTextIter next;
767         gchar *line_text;
768         const gchar *found;
769         gint offset;
770
771         if (*lines == NULL || **lines == '\0')
772         {
773                 if (match_start)
774                         *match_start = *start;
775                 if (match_end)
776                         *match_end = *start;
777                 return TRUE;
778         }
779
780         next = *start;
781         gtk_text_iter_forward_line (&next);
782
783         /* No more text in buffer, but *lines is nonempty */
784         if (gtk_text_iter_equal (start, &next))
785                 return FALSE;
786
787         if (slice)
788         {
789                 if (visible_only)
790                         line_text = gtk_text_iter_get_visible_slice (start, &next);
791                 else
792                         line_text = gtk_text_iter_get_slice (start, &next);
793         }
794         else
795         {
796                 if (visible_only)
797                         line_text = gtk_text_iter_get_visible_text (start, &next);
798                 else
799                         line_text = gtk_text_iter_get_text (start, &next);
800         }
801
802         if (match_start) /* if this is the first line we're matching */
803         {
804                 found = g_utf8_strcasestr (line_text, *lines);
805         }
806         else
807         {
808                 /* If it's not the first line, we have to match from the
809                  * start of the line.
810                  */
811                 if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
812                                            strlen (*lines)))
813                         found = line_text;
814                 else
815                         found = NULL;
816         }
817
818         if (found == NULL)
819         {
820                 g_free (line_text);
821                 return FALSE;
822         }
823
824         /* Get offset to start of search string */
825         offset = g_utf8_strlen (line_text, found - line_text);
826
827         next = *start;
828
829         /* If match start needs to be returned, set it to the
830          * start of the search string.
831          */
832         forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
833         if (match_start)
834         {
835                 *match_start = next;
836         }
837
838         /* Go to end of search string */
839         forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
840
841         g_free (line_text);
842
843         ++lines;
844
845         if (match_end)
846                 *match_end = next;
847
848         /* pass NULL for match_start, since we don't need to find the
849          * start again.
850          */
851         return lines_match (&next, lines, visible_only, slice, NULL, match_end);
852 }
853
854 /* strsplit () that retains the delimiter as part of the string. */
855 static gchar **
856 strbreakup (const char *string,
857             const char *delimiter,
858             gint        max_tokens)
859 {
860         GSList *string_list = NULL, *slist;
861         gchar **str_array, *s, *casefold, *new_string;
862         guint i, n = 1;
863
864         g_return_val_if_fail (string != NULL, NULL);
865         g_return_val_if_fail (delimiter != NULL, NULL);
866
867         if (max_tokens < 1)
868                 max_tokens = G_MAXINT;
869
870         s = strstr (string, delimiter);
871         if (s)
872         {
873                 guint delimiter_len = strlen (delimiter);
874
875                 do
876                 {
877                         guint len;
878
879                         len = s - string + delimiter_len;
880                         new_string = g_new (gchar, len + 1);
881                         strncpy (new_string, string, len);
882                         new_string[len] = 0;
883                         casefold = g_utf8_casefold (new_string, -1);
884                         g_free (new_string);
885                         new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
886                         g_free (casefold);
887                         string_list = g_slist_prepend (string_list, new_string);
888                         n++;
889                         string = s + delimiter_len;
890                         s = strstr (string, delimiter);
891                 } while (--max_tokens && s);
892         }
893
894         if (*string)
895         {
896                 n++;
897                 casefold = g_utf8_casefold (string, -1);
898                 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
899                 g_free (casefold);
900                 string_list = g_slist_prepend (string_list, new_string);
901         }
902
903         str_array = g_new (gchar*, n);
904
905         i = n - 1;
906
907         str_array[i--] = NULL;
908         for (slist = string_list; slist; slist = slist->next)
909                 str_array[i--] = slist->data;
910
911         g_slist_free (string_list);
912
913         return str_array;
914 }
915
916 gboolean
917 empathy_text_iter_forward_search (const GtkTextIter   *iter,
918                                  const gchar         *str,
919                                  GtkTextIter         *match_start,
920                                  GtkTextIter         *match_end,
921                                  const GtkTextIter   *limit)
922 {
923         gchar **lines = NULL;
924         GtkTextIter match;
925         gboolean retval = FALSE;
926         GtkTextIter search;
927         gboolean visible_only;
928         gboolean slice;
929
930         g_return_val_if_fail (iter != NULL, FALSE);
931         g_return_val_if_fail (str != NULL, FALSE);
932
933         if (limit && gtk_text_iter_compare (iter, limit) >= 0)
934                 return FALSE;
935
936         if (*str == '\0') {
937                 /* If we can move one char, return the empty string there */
938                 match = *iter;
939
940                 if (gtk_text_iter_forward_char (&match)) {
941                         if (limit && gtk_text_iter_equal (&match, limit)) {
942                                 return FALSE;
943                         }
944
945                         if (match_start) {
946                                 *match_start = match;
947                         }
948                         if (match_end) {
949                                 *match_end = match;
950                         }
951                         return TRUE;
952                 } else {
953                         return FALSE;
954                 }
955         }
956
957         visible_only = TRUE;
958         slice = FALSE;
959
960         /* locate all lines */
961         lines = strbreakup (str, "\n", -1);
962
963         search = *iter;
964
965         do {
966                 /* This loop has an inefficient worst-case, where
967                  * gtk_text_iter_get_text () is called repeatedly on
968                  * a single line.
969                  */
970                 GtkTextIter end;
971
972                 if (limit && gtk_text_iter_compare (&search, limit) >= 0) {
973                         break;
974                 }
975
976                 if (lines_match (&search, (const gchar**)lines,
977                                  visible_only, slice, &match, &end)) {
978                         if (limit == NULL ||
979                             (limit && gtk_text_iter_compare (&end, limit) <= 0)) {
980                                 retval = TRUE;
981
982                                 if (match_start) {
983                                         *match_start = match;
984                                 }
985                                 if (match_end) {
986                                         *match_end = end;
987                                 }
988                         }
989                         break;
990                 }
991         } while (gtk_text_iter_forward_line (&search));
992
993         g_strfreev ((gchar **) lines);
994
995         return retval;
996 }
997
998 static const gchar *
999 g_utf8_strrcasestr (const gchar *haystack, const gchar *needle)
1000 {
1001         gsize needle_len;
1002         gsize haystack_len;
1003         const gchar *ret = NULL;
1004         gchar *p;
1005         gchar *casefold;
1006         gchar *caseless_haystack;
1007         gint i;
1008
1009         g_return_val_if_fail (haystack != NULL, NULL);
1010         g_return_val_if_fail (needle != NULL, NULL);
1011
1012         casefold = g_utf8_casefold (haystack, -1);
1013         caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1014         g_free (casefold);
1015
1016         needle_len = g_utf8_strlen (needle, -1);
1017         haystack_len = g_utf8_strlen (caseless_haystack, -1);
1018
1019         if (needle_len == 0)
1020         {
1021                 ret = (gchar *) haystack;
1022                 goto finally_1;
1023         }
1024
1025         if (haystack_len < needle_len)
1026         {
1027                 ret = NULL;
1028                 goto finally_1;
1029         }
1030
1031         i = haystack_len - needle_len;
1032         p = g_utf8_offset_to_pointer (caseless_haystack, i);
1033         needle_len = strlen (needle);
1034
1035         while (p >= caseless_haystack)
1036         {
1037                 if (strncmp (p, needle, needle_len) == 0)
1038                 {
1039                         ret = pointer_from_offset_skipping_decomp (haystack, i);
1040                         goto finally_1;
1041                 }
1042
1043                 p = g_utf8_prev_char (p);
1044                 i--;
1045         }
1046
1047 finally_1:
1048         g_free (caseless_haystack);
1049
1050         return ret;
1051 }
1052
1053 static gboolean
1054 backward_lines_match (const GtkTextIter *start,
1055                       const gchar      **lines,
1056                       gboolean           visible_only,
1057                       gboolean           slice,
1058                       GtkTextIter       *match_start,
1059                       GtkTextIter       *match_end)
1060 {
1061         GtkTextIter line, next;
1062         gchar *line_text;
1063         const gchar *found;
1064         gint offset;
1065
1066         if (*lines == NULL || **lines == '\0')
1067         {
1068                 if (match_start)
1069                         *match_start = *start;
1070                 if (match_end)
1071                         *match_end = *start;
1072                 return TRUE;
1073         }
1074
1075         line = next = *start;
1076         if (gtk_text_iter_get_line_offset (&next) == 0)
1077         {
1078                 if (!gtk_text_iter_backward_line (&next))
1079                         return FALSE;
1080         }
1081         else
1082                 gtk_text_iter_set_line_offset (&next, 0);
1083
1084         if (slice)
1085         {
1086                 if (visible_only)
1087                         line_text = gtk_text_iter_get_visible_slice (&next, &line);
1088                 else
1089                         line_text = gtk_text_iter_get_slice (&next, &line);
1090         }
1091         else
1092         {
1093                 if (visible_only)
1094                         line_text = gtk_text_iter_get_visible_text (&next, &line);
1095                 else
1096                         line_text = gtk_text_iter_get_text (&next, &line);
1097         }
1098
1099         if (match_start) /* if this is the first line we're matching */
1100         {
1101                 found = g_utf8_strrcasestr (line_text, *lines);
1102         }
1103         else
1104         {
1105                 /* If it's not the first line, we have to match from the
1106                  * start of the line.
1107                  */
1108                 if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
1109                                            strlen (*lines)))
1110                         found = line_text;
1111                 else
1112                         found = NULL;
1113         }
1114
1115         if (found == NULL)
1116         {
1117                 g_free (line_text);
1118                 return FALSE;
1119         }
1120
1121         /* Get offset to start of search string */
1122         offset = g_utf8_strlen (line_text, found - line_text);
1123
1124         forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
1125
1126         /* If match start needs to be returned, set it to the
1127          * start of the search string.
1128          */
1129         if (match_start)
1130         {
1131                 *match_start = next;
1132         }
1133
1134         /* Go to end of search string */
1135         forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
1136
1137         g_free (line_text);
1138
1139         ++lines;
1140
1141         if (match_end)
1142                 *match_end = next;
1143
1144         /* try to match the rest of the lines forward, passing NULL
1145          * for match_start so lines_match will try to match the entire
1146          * line */
1147         return lines_match (&next, lines, visible_only,
1148                             slice, NULL, match_end);
1149 }
1150
1151 gboolean
1152 empathy_text_iter_backward_search (const GtkTextIter   *iter,
1153                                   const gchar         *str,
1154                                   GtkTextIter         *match_start,
1155                                   GtkTextIter         *match_end,
1156                                   const GtkTextIter   *limit)
1157 {
1158         gchar **lines = NULL;
1159         GtkTextIter match;
1160         gboolean retval = FALSE;
1161         GtkTextIter search;
1162         gboolean visible_only;
1163         gboolean slice;
1164
1165         g_return_val_if_fail (iter != NULL, FALSE);
1166         g_return_val_if_fail (str != NULL, FALSE);
1167
1168         if (limit && gtk_text_iter_compare (iter, limit) <= 0)
1169                 return FALSE;
1170
1171         if (*str == '\0')
1172         {
1173                 /* If we can move one char, return the empty string there */
1174                 match = *iter;
1175
1176                 if (gtk_text_iter_backward_char (&match))
1177                 {
1178                         if (limit && gtk_text_iter_equal (&match, limit))
1179                                 return FALSE;
1180
1181                         if (match_start)
1182                                 *match_start = match;
1183                         if (match_end)
1184                                 *match_end = match;
1185                         return TRUE;
1186                 }
1187                 else
1188                 {
1189                         return FALSE;
1190                 }
1191         }
1192
1193         visible_only = TRUE;
1194         slice = TRUE;
1195
1196         /* locate all lines */
1197         lines = strbreakup (str, "\n", -1);
1198
1199         search = *iter;
1200
1201         while (TRUE)
1202         {
1203                 /* This loop has an inefficient worst-case, where
1204                  * gtk_text_iter_get_text () is called repeatedly on
1205                  * a single line.
1206                  */
1207                 GtkTextIter end;
1208
1209                 if (limit && gtk_text_iter_compare (&search, limit) <= 0)
1210                         break;
1211
1212                 if (backward_lines_match (&search, (const gchar**)lines,
1213                                           visible_only, slice, &match, &end))
1214                 {
1215                         if (limit == NULL || (limit &&
1216                                               gtk_text_iter_compare (&end, limit) > 0))
1217                         {
1218                                 retval = TRUE;
1219
1220                                 if (match_start)
1221                                         *match_start = match;
1222                                 if (match_end)
1223                                         *match_end = end;
1224                         }
1225                         break;
1226                 }
1227
1228                 if (gtk_text_iter_get_line_offset (&search) == 0)
1229                 {
1230                         if (!gtk_text_iter_backward_line (&search))
1231                                 break;
1232                 }
1233                 else
1234                 {
1235                         gtk_text_iter_set_line_offset (&search, 0);
1236                 }
1237         }
1238
1239         g_strfreev ((gchar **) lines);
1240
1241         return retval;
1242 }
1243
1244 gboolean
1245 empathy_window_get_is_visible (GtkWindow *window)
1246 {
1247         GdkWindowState  state;
1248         GdkWindow      *gdk_window;
1249
1250         g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
1251
1252         gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1253         if (!gdk_window) {
1254                 return FALSE;
1255         }
1256
1257         state = gdk_window_get_state (gdk_window);
1258         if (state & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED)) {
1259                 return FALSE;
1260         }
1261
1262         return TRUE;
1263 }
1264
1265 void
1266 empathy_window_iconify (GtkWindow *window, GtkStatusIcon *status_icon)
1267 {
1268         GdkRectangle  icon_location;
1269         gulong        data[4];
1270         Display      *dpy;
1271         GdkWindow    *gdk_window;
1272
1273         gtk_status_icon_get_geometry (status_icon, NULL, &icon_location, NULL);
1274         gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1275         dpy = gdk_x11_drawable_get_xdisplay (gdk_window);
1276
1277         data[0] = icon_location.x;
1278         data[1] = icon_location.y;
1279         data[2] = icon_location.width;
1280         data[3] = icon_location.height;
1281
1282         XChangeProperty (dpy,
1283                          GDK_WINDOW_XID (gdk_window),
1284                          gdk_x11_get_xatom_by_name_for_display (gdk_drawable_get_display (gdk_window),
1285                          "_NET_WM_ICON_GEOMETRY"),
1286                          XA_CARDINAL, 32, PropModeReplace,
1287                          (guchar *)&data, 4);
1288
1289         gtk_window_set_skip_taskbar_hint (window, TRUE);
1290         gtk_window_iconify (window);
1291 }
1292
1293 /* Takes care of moving the window to the current workspace. */
1294 void
1295 empathy_window_present (GtkWindow *window,
1296                         gboolean   steal_focus)
1297 {
1298         guint32 timestamp;
1299         GdkWindow *gdk_window;
1300
1301         g_return_if_fail (GTK_IS_WINDOW (window));
1302
1303         /* Move the window to the current workspace before trying to show it.
1304          * This is the behaviour people expect when clicking on the statusbar icon. */
1305         gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1306         if (gdk_window) {
1307                 gint x, y;
1308                 gint w, h;
1309
1310                 /* Has no effect if the WM has viewports, like compiz */
1311                 gdk_x11_window_move_to_current_desktop (gdk_window);
1312
1313                 /* If window is still off-screen, hide it to force it to
1314                  * reposition on the current workspace. */
1315                 gtk_window_get_position (window, &x, &y);
1316                 gtk_window_get_size (window, &w, &h);
1317                 if (!EMPATHY_RECT_IS_ON_SCREEN (x, y, w, h))
1318                         gtk_widget_hide (GTK_WIDGET (window));
1319         }
1320
1321         timestamp = gtk_get_current_event_time ();
1322         gtk_window_present_with_time (window, timestamp);
1323         gtk_window_set_skip_taskbar_hint (window, FALSE);
1324         gtk_window_deiconify (window);
1325 }
1326
1327 GtkWindow *
1328 empathy_get_toplevel_window (GtkWidget *widget)
1329 {
1330         GtkWidget *toplevel;
1331
1332         g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
1333
1334         toplevel = gtk_widget_get_toplevel (widget);
1335         if (GTK_IS_WINDOW (toplevel) &&
1336             gtk_widget_is_toplevel (toplevel)) {
1337                 return GTK_WINDOW (toplevel);
1338         }
1339
1340         return NULL;
1341 }
1342
1343 /** empathy_make_absolute_url_len:
1344  * @url: an url
1345  * @len: a length
1346  *
1347  * Same as #empathy_make_absolute_url but for a limited string length
1348  */
1349 gchar *
1350 empathy_make_absolute_url_len (const gchar *url,
1351                                guint len)
1352 {
1353         g_return_val_if_fail (url != NULL, NULL);
1354
1355         if (g_str_has_prefix (url, "ghelp:") ||
1356             g_str_has_prefix (url, "mailto:") ||
1357             strstr (url, ":/")) {
1358                 return g_strndup (url, len);
1359         }
1360
1361         if (strstr (url, "@")) {
1362                 return g_strdup_printf ("mailto:%.*s", len, url);
1363         }
1364
1365         return g_strdup_printf ("http://%.*s", len, url);
1366 }
1367
1368 /** empathy_make_absolute_url:
1369  * @url: an url
1370  *
1371  * The URL opening code can't handle schemeless strings, so we try to be
1372  * smart and add http if there is no scheme or doesn't look like a mail
1373  * address. This should work in most cases, and let us click on strings
1374  * like "www.gnome.org".
1375  *
1376  * Returns: a newly allocated url with proper mailto: or http:// prefix, use
1377  * g_free when your are done with it
1378  */
1379 gchar *
1380 empathy_make_absolute_url (const gchar *url)
1381 {
1382         return empathy_make_absolute_url_len (url, strlen (url));
1383 }
1384
1385 void
1386 empathy_url_show (GtkWidget *parent,
1387                   const char *url)
1388 {
1389         gchar  *real_url;
1390         GError *error = NULL;
1391
1392         g_return_if_fail (parent == NULL || GTK_IS_WIDGET (parent));
1393         g_return_if_fail (url != NULL);
1394
1395         real_url = empathy_make_absolute_url (url);
1396
1397         gtk_show_uri (parent ? gtk_widget_get_screen (parent) : NULL, real_url,
1398                       gtk_get_current_event_time (), &error);
1399
1400         if (error) {
1401                 GtkWidget *dialog;
1402
1403                 dialog = gtk_message_dialog_new (NULL, 0,
1404                                                  GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
1405                                                  _("Unable to open URI"));
1406                 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1407                                                           "%s", error->message);
1408
1409                 g_signal_connect (dialog, "response",
1410                                   G_CALLBACK (gtk_widget_destroy),
1411                                   NULL);
1412                 gtk_window_present (GTK_WINDOW (dialog));
1413
1414                 g_clear_error (&error);
1415         }
1416
1417         g_free (real_url);
1418 }
1419
1420 static void
1421 link_button_hook (GtkLinkButton *button,
1422                   const gchar *link,
1423                   gpointer user_data)
1424 {
1425         empathy_url_show (GTK_WIDGET (button), link);
1426 }
1427
1428 GtkWidget *
1429 empathy_link_button_new (const gchar *url,
1430                         const gchar *title)
1431 {
1432         static gboolean hook = FALSE;
1433
1434         if (!hook) {
1435                 hook = TRUE;
1436                 gtk_link_button_set_uri_hook (link_button_hook, NULL, NULL);
1437         }
1438
1439         return gtk_link_button_new_with_label (url, title);
1440 }
1441
1442 void
1443 empathy_toggle_button_set_state_quietly (GtkWidget *widget,
1444                                         GCallback  callback,
1445                                         gpointer   user_data,
1446                                         gboolean   active)
1447 {
1448         g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
1449
1450         g_signal_handlers_block_by_func (widget, callback, user_data);
1451         g_object_set (widget, "active", active, NULL);
1452         g_signal_handlers_unblock_by_func (widget, callback, user_data);
1453 }
1454
1455 static void
1456 file_manager_send_file_response_cb (GtkDialog      *widget,
1457                                     gint            response_id,
1458                                     EmpathyContact *contact)
1459 {
1460         EmpathyFTFactory *factory;
1461         GFile *file;
1462         gchar *uri;
1463         GtkRecentManager *manager;
1464
1465         if (response_id == GTK_RESPONSE_OK) {
1466                 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget));
1467                 uri = g_file_get_uri (file);
1468
1469                 factory = empathy_ft_factory_dup_singleton ();
1470
1471                 empathy_ft_factory_new_transfer_outgoing (factory, contact,
1472                                                           file);
1473
1474                 manager = gtk_recent_manager_get_default ();
1475                 gtk_recent_manager_add_item (manager, uri);
1476
1477                 g_free (uri);
1478                 g_object_unref (factory);
1479                 g_object_unref (file);
1480         }
1481
1482         gtk_widget_destroy (GTK_WIDGET (widget));
1483 }
1484
1485 void
1486 empathy_send_file_with_file_chooser (EmpathyContact *contact)
1487 {
1488         GtkWidget               *widget;
1489         GtkWidget               *button;
1490
1491         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1492
1493         DEBUG ("Creating selection file chooser");
1494
1495         widget = gtk_file_chooser_dialog_new (_("Select a file"),
1496                                               NULL,
1497                                               GTK_FILE_CHOOSER_ACTION_OPEN,
1498                                               GTK_STOCK_CANCEL,
1499                                               GTK_RESPONSE_CANCEL,
1500                                               NULL);
1501
1502         /* send button */
1503         button = gtk_button_new_with_mnemonic (_("_Send"));
1504         gtk_button_set_image (GTK_BUTTON (button),
1505                 gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1506                                               GTK_ICON_SIZE_BUTTON));
1507         gtk_widget_show (button);
1508         gtk_dialog_add_action_widget (GTK_DIALOG (widget), button,
1509                                       GTK_RESPONSE_OK);
1510         gtk_widget_set_can_default (button, TRUE);
1511         gtk_dialog_set_default_response (GTK_DIALOG (widget),
1512                                          GTK_RESPONSE_OK);
1513
1514         gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), FALSE);
1515
1516         g_signal_connect (widget, "response",
1517                           G_CALLBACK (file_manager_send_file_response_cb),
1518                           contact);
1519
1520         gtk_widget_show (widget);
1521 }
1522
1523 static void
1524 file_manager_receive_file_response_cb (GtkDialog *dialog,
1525                                        GtkResponseType response,
1526                                        EmpathyFTHandler *handler)
1527 {
1528         EmpathyFTFactory *factory;
1529         GFile *file;
1530
1531         if (response == GTK_RESPONSE_OK) {
1532                 factory = empathy_ft_factory_dup_singleton ();
1533                 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
1534
1535                 empathy_ft_factory_set_destination_for_incoming_handler
1536                         (factory, handler, file);
1537
1538                 g_object_unref (factory);
1539                 g_object_unref (file);
1540         } else {
1541                 /* unref the handler, as we dismissed the file chooser,
1542                  * and refused the transfer.
1543                  */
1544                 g_object_unref (handler);
1545         }
1546
1547         gtk_widget_destroy (GTK_WIDGET (dialog));
1548 }
1549
1550 void
1551 empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
1552 {
1553         GtkWidget *widget;
1554
1555         widget = gtk_file_chooser_dialog_new (_("Select a destination"),
1556                                               NULL,
1557                                               GTK_FILE_CHOOSER_ACTION_SAVE,
1558                                               GTK_STOCK_CANCEL,
1559                                               GTK_RESPONSE_CANCEL,
1560                                               GTK_STOCK_SAVE,
1561                                               GTK_RESPONSE_OK,
1562                                               NULL);
1563         gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget),
1564                 empathy_ft_handler_get_filename (handler));
1565         gtk_file_chooser_set_do_overwrite_confirmation
1566                 (GTK_FILE_CHOOSER (widget), TRUE);
1567
1568         g_signal_connect (widget, "response",
1569                 G_CALLBACK (file_manager_receive_file_response_cb), handler);
1570
1571         gtk_widget_show (widget);
1572 }
1573
1574 void
1575 empathy_string_parser_substr (GString *string,
1576                               const gchar *text,
1577                               gssize len,
1578                               EmpathyStringParser *parsers)
1579 {
1580         if (parsers != NULL && parsers[0].match_func != NULL) {
1581                 parsers[0].match_func (string, text, len,
1582                         parsers[0].replace_func, parsers + 1);
1583         } else {
1584                 g_string_append_len (string, text, len);
1585         }
1586 }
1587
1588 void
1589 empathy_string_match_link (GString *string,
1590                            const gchar *text,
1591                            gssize len,
1592                            EmpathyStringReplace replace_func,
1593                            EmpathyStringParser *sub_parsers)
1594 {
1595         GRegex     *uri_regex;
1596         GMatchInfo *match_info;
1597         gboolean    match;
1598         gint        last = 0;
1599
1600         uri_regex = empathy_uri_regex_dup_singleton ();
1601         match = g_regex_match_full (uri_regex, text, len, 0, 0, &match_info, NULL);
1602         if (match) {
1603                 gint s = 0, e = 0;
1604
1605                 do {
1606                         g_match_info_fetch_pos (match_info, 0, &s, &e);
1607
1608                         if (s > last) {
1609                                 /* Append the text between last link (or the
1610                                  * start of the message) and this link */
1611                                 empathy_string_parser_substr (string, text + last,
1612                                                               s - last,
1613                                                               sub_parsers);
1614                         }
1615
1616                         replace_func (string, text + s, e - s, NULL);
1617
1618                         last = e;
1619                 } while (g_match_info_next (match_info, NULL));
1620         }
1621
1622         empathy_string_parser_substr (string, text + last, len - last, sub_parsers);
1623
1624         g_match_info_free (match_info);
1625         g_regex_unref (uri_regex);
1626 }
1627
1628 void
1629 empathy_string_match_smiley (GString *string,
1630                              const gchar *text,
1631                              gssize len,
1632                              EmpathyStringReplace replace_func,
1633                              EmpathyStringParser *sub_parsers)
1634 {
1635         guint last = 0;
1636         EmpathySmileyManager *smiley_manager;
1637         GSList *hits, *l;
1638
1639         smiley_manager = empathy_smiley_manager_dup_singleton ();
1640         hits = empathy_smiley_manager_parse_len (smiley_manager, text, len);
1641
1642         for (l = hits; l; l = l->next) {
1643                 EmpathySmileyHit *hit = l->data;
1644
1645                 if (hit->start > last) {
1646                         /* Append the text between last smiley (or the
1647                          * start of the message) and this smiley */
1648                         empathy_string_parser_substr (string, text + last,
1649                                                       hit->start - last,
1650                                                       sub_parsers);
1651                 }
1652
1653                 replace_func (string,
1654                               text + hit->start, hit->end - hit->start,
1655                               hit);
1656
1657                 last = hit->end;
1658
1659                 empathy_smiley_hit_free (hit);
1660         }
1661         g_slist_free (hits);
1662         g_object_unref (smiley_manager);
1663
1664         empathy_string_parser_substr (string, text + last, len - last, sub_parsers);
1665 }
1666
1667 void
1668 empathy_string_match_escape (GString *string,
1669                              const gchar *text,
1670                              gssize len,
1671                              EmpathyStringReplace replace_func,
1672                              EmpathyStringParser *sub_parsers)
1673 {
1674         gchar *escaped;
1675
1676         escaped = g_markup_escape_text (text, len);
1677         g_string_append (string, escaped);
1678         g_free (escaped);
1679 }
1680