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