]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-ui-utils.c
contact_list: displays an icon showing the protocol by the contact (#603877)
[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
539         numerator = 3;
540         denominator = 4;
541
542         icon_filename = empathy_filename_from_icon_name (icon_name,
543                                                          GTK_ICON_SIZE_MENU);
544         if (icon_filename == NULL) {
545                 DEBUG ("icon name: %s could not be found\n", icon_name);
546                 return NULL;
547         }
548
549         pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
550
551         g_free (icon_filename);
552
553         if (pix_status == NULL) {
554                 DEBUG ("Could not open icon %s\n", icon_filename);
555                 return NULL;
556         }
557
558         if (!show_protocol)
559                 return pix_status;
560
561         height = gdk_pixbuf_get_height (pix_status);
562         width = gdk_pixbuf_get_width (pix_status);
563
564         pix_protocol = empathy_pixbuf_protocol_from_contact_scaled (contact,
565                                                                     width * numerator / denominator,
566                                                                     height * numerator / denominator);
567
568         if (pix_protocol == NULL) {
569                 return pix_status;
570         }
571         gdk_pixbuf_composite (pix_protocol, pix_status,
572             0, height - height * numerator / denominator,
573             width * numerator / denominator, height * numerator / denominator,
574             0, height - height * numerator / denominator,
575             1, 1,
576             GDK_INTERP_BILINEAR, 255);
577
578         g_object_unref (pix_protocol);
579
580         return pix_status;
581 }
582
583 GdkPixbuf *
584 empathy_pixbuf_protocol_from_contact_scaled (EmpathyContact *contact,
585                                           gint           width,
586                                           gint           height)
587 {
588         TpAccount *account;
589         gchar     *filename;
590         GdkPixbuf *pixbuf = NULL;
591
592         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
593
594         account = empathy_contact_get_account (contact);
595         filename = empathy_filename_from_icon_name (tp_account_get_icon_name (account),
596                                                     GTK_ICON_SIZE_MENU);
597         if (filename != NULL) {
598                 pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width, height, NULL);
599                 g_free (filename);
600         }
601
602         return pixbuf;
603 }
604
605 GdkPixbuf *
606 empathy_pixbuf_scale_down_if_necessary (GdkPixbuf *pixbuf, gint max_size)
607 {
608         gint      width, height;
609         gdouble   factor;
610
611         width = gdk_pixbuf_get_width (pixbuf);
612         height = gdk_pixbuf_get_height (pixbuf);
613
614         if (width > 0 && (width > max_size || height > max_size)) {
615                 factor = (gdouble) max_size / MAX (width, height);
616
617                 width = width * factor;
618                 height = height * factor;
619
620                 return gdk_pixbuf_scale_simple (pixbuf,
621                                                 width, height,
622                                                 GDK_INTERP_HYPER);
623         }
624
625         return g_object_ref (pixbuf);
626 }
627
628 GdkPixbuf *
629 empathy_pixbuf_from_icon_name_sized (const gchar *icon_name,
630                                      gint size)
631 {
632         GtkIconTheme *theme;
633         GdkPixbuf *pixbuf;
634         GError *error = NULL;
635
636         if (!icon_name) {
637                 return NULL;
638         }
639
640         theme = gtk_icon_theme_get_default ();
641
642         pixbuf = gtk_icon_theme_load_icon (theme,
643                                            icon_name,
644                                            size,
645                                            0,
646                                            &error);
647         if (error) {
648                 DEBUG ("Error loading icon: %s", error->message);
649                 g_clear_error (&error);
650         }
651
652         return pixbuf;
653 }
654
655 GdkPixbuf *
656 empathy_pixbuf_from_icon_name (const gchar *icon_name,
657                                GtkIconSize  icon_size)
658 {
659         gint  w, h;
660         gint  size = 48;
661
662         if (!icon_name) {
663                 return NULL;
664         }
665
666         if (gtk_icon_size_lookup (icon_size, &w, &h)) {
667                 size = (w + h) / 2;
668         }
669
670         return empathy_pixbuf_from_icon_name_sized (icon_name, size);
671 }
672
673 gchar *
674 empathy_filename_from_icon_name (const gchar *icon_name,
675                                  GtkIconSize  icon_size)
676 {
677         GtkIconTheme *icon_theme;
678         GtkIconInfo  *icon_info;
679         gint          w, h;
680         gint          size = 48;
681         gchar        *ret;
682
683         icon_theme = gtk_icon_theme_get_default ();
684
685         if (gtk_icon_size_lookup (icon_size, &w, &h)) {
686                 size = (w + h) / 2;
687         }
688
689         icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
690         ret = g_strdup (gtk_icon_info_get_filename (icon_info));
691         gtk_icon_info_free (icon_info);
692
693         return ret;
694 }
695
696 /* Stolen from GtkSourceView, hence the weird intendation. Please keep it like
697  * that to make it easier to apply changes from the original code.
698  */
699 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
700
701 /* this function acts like g_utf8_offset_to_pointer() except that if it finds a
702  * decomposable character it consumes the decomposition length from the given
703  * offset.  So it's useful when the offset was calculated for the normalized
704  * version of str, but we need a pointer to str itself. */
705 static const gchar *
706 pointer_from_offset_skipping_decomp (const gchar *str, gint offset)
707 {
708         gchar *casefold, *normal;
709         const gchar *p, *q;
710
711         p = str;
712         while (offset > 0)
713         {
714                 q = g_utf8_next_char (p);
715                 casefold = g_utf8_casefold (p, q - p);
716                 normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
717                 offset -= g_utf8_strlen (normal, -1);
718                 g_free (casefold);
719                 g_free (normal);
720                 p = q;
721         }
722         return p;
723 }
724
725 static const gchar *
726 g_utf8_strcasestr (const gchar *haystack, const gchar *needle)
727 {
728         gsize needle_len;
729         gsize haystack_len;
730         const gchar *ret = NULL;
731         gchar *p;
732         gchar *casefold;
733         gchar *caseless_haystack;
734         gint i;
735
736         g_return_val_if_fail (haystack != NULL, NULL);
737         g_return_val_if_fail (needle != NULL, NULL);
738
739         casefold = g_utf8_casefold (haystack, -1);
740         caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
741         g_free (casefold);
742
743         needle_len = g_utf8_strlen (needle, -1);
744         haystack_len = g_utf8_strlen (caseless_haystack, -1);
745
746         if (needle_len == 0)
747         {
748                 ret = (gchar *) haystack;
749                 goto finally_1;
750         }
751
752         if (haystack_len < needle_len)
753         {
754                 ret = NULL;
755                 goto finally_1;
756         }
757
758         p = (gchar *) caseless_haystack;
759         needle_len = strlen (needle);
760         i = 0;
761
762         while (*p)
763         {
764                 if ((strncmp (p, needle, needle_len) == 0))
765                 {
766                         ret = pointer_from_offset_skipping_decomp (haystack, i);
767                         goto finally_1;
768                 }
769
770                 p = g_utf8_next_char (p);
771                 i++;
772         }
773
774 finally_1:
775         g_free (caseless_haystack);
776
777         return ret;
778 }
779
780 static gboolean
781 g_utf8_caselessnmatch (const char *s1, const char *s2,
782                        gssize n1, gssize n2)
783 {
784         gchar *casefold;
785         gchar *normalized_s1;
786         gchar *normalized_s2;
787         gint len_s1;
788         gint len_s2;
789         gboolean ret = FALSE;
790
791         g_return_val_if_fail (s1 != NULL, FALSE);
792         g_return_val_if_fail (s2 != NULL, FALSE);
793         g_return_val_if_fail (n1 > 0, FALSE);
794         g_return_val_if_fail (n2 > 0, FALSE);
795
796         casefold = g_utf8_casefold (s1, n1);
797         normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
798         g_free (casefold);
799
800         casefold = g_utf8_casefold (s2, n2);
801         normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
802         g_free (casefold);
803
804         len_s1 = strlen (normalized_s1);
805         len_s2 = strlen (normalized_s2);
806
807         if (len_s1 < len_s2)
808                 goto finally_2;
809
810         ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
811
812 finally_2:
813         g_free (normalized_s1);
814         g_free (normalized_s2);
815
816         return ret;
817 }
818
819 static void
820 forward_chars_with_skipping (GtkTextIter *iter,
821                              gint         count,
822                              gboolean     skip_invisible,
823                              gboolean     skip_nontext,
824                              gboolean     skip_decomp)
825 {
826         gint i;
827
828         g_return_if_fail (count >= 0);
829
830         i = count;
831
832         while (i > 0)
833         {
834                 gboolean ignored = FALSE;
835
836                 /* minimal workaround to avoid the infinite loop of bug #168247.
837                  * It doesn't fix the problemjust the symptom...
838                  */
839                 if (gtk_text_iter_is_end (iter))
840                         return;
841
842                 if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR)
843                         ignored = TRUE;
844
845                 if (!ignored && skip_invisible &&
846                     /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE)
847                         ignored = TRUE;
848
849                 if (!ignored && skip_decomp)
850                 {
851                         /* being UTF8 correct sucks; this accounts for extra
852                            offsets coming from canonical decompositions of
853                            UTF8 characters (e.g. accented characters) which
854                            g_utf8_normalize () performs */
855                         gchar *normal;
856                         gchar buffer[6];
857                         gint buffer_len;
858
859                         buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer);
860                         normal = g_utf8_normalize (buffer, buffer_len, G_NORMALIZE_NFD);
861                         i -= (g_utf8_strlen (normal, -1) - 1);
862                         g_free (normal);
863                 }
864
865                 gtk_text_iter_forward_char (iter);
866
867                 if (!ignored)
868                         --i;
869         }
870 }
871
872 static gboolean
873 lines_match (const GtkTextIter *start,
874              const gchar      **lines,
875              gboolean           visible_only,
876              gboolean           slice,
877              GtkTextIter       *match_start,
878              GtkTextIter       *match_end)
879 {
880         GtkTextIter next;
881         gchar *line_text;
882         const gchar *found;
883         gint offset;
884
885         if (*lines == NULL || **lines == '\0')
886         {
887                 if (match_start)
888                         *match_start = *start;
889                 if (match_end)
890                         *match_end = *start;
891                 return TRUE;
892         }
893
894         next = *start;
895         gtk_text_iter_forward_line (&next);
896
897         /* No more text in buffer, but *lines is nonempty */
898         if (gtk_text_iter_equal (start, &next))
899                 return FALSE;
900
901         if (slice)
902         {
903                 if (visible_only)
904                         line_text = gtk_text_iter_get_visible_slice (start, &next);
905                 else
906                         line_text = gtk_text_iter_get_slice (start, &next);
907         }
908         else
909         {
910                 if (visible_only)
911                         line_text = gtk_text_iter_get_visible_text (start, &next);
912                 else
913                         line_text = gtk_text_iter_get_text (start, &next);
914         }
915
916         if (match_start) /* if this is the first line we're matching */
917         {
918                 found = g_utf8_strcasestr (line_text, *lines);
919         }
920         else
921         {
922                 /* If it's not the first line, we have to match from the
923                  * start of the line.
924                  */
925                 if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
926                                            strlen (*lines)))
927                         found = line_text;
928                 else
929                         found = NULL;
930         }
931
932         if (found == NULL)
933         {
934                 g_free (line_text);
935                 return FALSE;
936         }
937
938         /* Get offset to start of search string */
939         offset = g_utf8_strlen (line_text, found - line_text);
940
941         next = *start;
942
943         /* If match start needs to be returned, set it to the
944          * start of the search string.
945          */
946         forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
947         if (match_start)
948         {
949                 *match_start = next;
950         }
951
952         /* Go to end of search string */
953         forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
954
955         g_free (line_text);
956
957         ++lines;
958
959         if (match_end)
960                 *match_end = next;
961
962         /* pass NULL for match_start, since we don't need to find the
963          * start again.
964          */
965         return lines_match (&next, lines, visible_only, slice, NULL, match_end);
966 }
967
968 /* strsplit () that retains the delimiter as part of the string. */
969 static gchar **
970 strbreakup (const char *string,
971             const char *delimiter,
972             gint        max_tokens)
973 {
974         GSList *string_list = NULL, *slist;
975         gchar **str_array, *s, *casefold, *new_string;
976         guint i, n = 1;
977
978         g_return_val_if_fail (string != NULL, NULL);
979         g_return_val_if_fail (delimiter != NULL, NULL);
980
981         if (max_tokens < 1)
982                 max_tokens = G_MAXINT;
983
984         s = strstr (string, delimiter);
985         if (s)
986         {
987                 guint delimiter_len = strlen (delimiter);
988
989                 do
990                 {
991                         guint len;
992
993                         len = s - string + delimiter_len;
994                         new_string = g_new (gchar, len + 1);
995                         strncpy (new_string, string, len);
996                         new_string[len] = 0;
997                         casefold = g_utf8_casefold (new_string, -1);
998                         g_free (new_string);
999                         new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1000                         g_free (casefold);
1001                         string_list = g_slist_prepend (string_list, new_string);
1002                         n++;
1003                         string = s + delimiter_len;
1004                         s = strstr (string, delimiter);
1005                 } while (--max_tokens && s);
1006         }
1007
1008         if (*string)
1009         {
1010                 n++;
1011                 casefold = g_utf8_casefold (string, -1);
1012                 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1013                 g_free (casefold);
1014                 string_list = g_slist_prepend (string_list, new_string);
1015         }
1016
1017         str_array = g_new (gchar*, n);
1018
1019         i = n - 1;
1020
1021         str_array[i--] = NULL;
1022         for (slist = string_list; slist; slist = slist->next)
1023                 str_array[i--] = slist->data;
1024
1025         g_slist_free (string_list);
1026
1027         return str_array;
1028 }
1029
1030 gboolean
1031 empathy_text_iter_forward_search (const GtkTextIter   *iter,
1032                                  const gchar         *str,
1033                                  GtkTextIter         *match_start,
1034                                  GtkTextIter         *match_end,
1035                                  const GtkTextIter   *limit)
1036 {
1037         gchar **lines = NULL;
1038         GtkTextIter match;
1039         gboolean retval = FALSE;
1040         GtkTextIter search;
1041         gboolean visible_only;
1042         gboolean slice;
1043
1044         g_return_val_if_fail (iter != NULL, FALSE);
1045         g_return_val_if_fail (str != NULL, FALSE);
1046
1047         if (limit && gtk_text_iter_compare (iter, limit) >= 0)
1048                 return FALSE;
1049
1050         if (*str == '\0') {
1051                 /* If we can move one char, return the empty string there */
1052                 match = *iter;
1053
1054                 if (gtk_text_iter_forward_char (&match)) {
1055                         if (limit && gtk_text_iter_equal (&match, limit)) {
1056                                 return FALSE;
1057                         }
1058
1059                         if (match_start) {
1060                                 *match_start = match;
1061                         }
1062                         if (match_end) {
1063                                 *match_end = match;
1064                         }
1065                         return TRUE;
1066                 } else {
1067                         return FALSE;
1068                 }
1069         }
1070
1071         visible_only = TRUE;
1072         slice = FALSE;
1073
1074         /* locate all lines */
1075         lines = strbreakup (str, "\n", -1);
1076
1077         search = *iter;
1078
1079         do {
1080                 /* This loop has an inefficient worst-case, where
1081                  * gtk_text_iter_get_text () is called repeatedly on
1082                  * a single line.
1083                  */
1084                 GtkTextIter end;
1085
1086                 if (limit && gtk_text_iter_compare (&search, limit) >= 0) {
1087                         break;
1088                 }
1089
1090                 if (lines_match (&search, (const gchar**)lines,
1091                                  visible_only, slice, &match, &end)) {
1092                         if (limit == NULL ||
1093                             (limit && gtk_text_iter_compare (&end, limit) <= 0)) {
1094                                 retval = TRUE;
1095
1096                                 if (match_start) {
1097                                         *match_start = match;
1098                                 }
1099                                 if (match_end) {
1100                                         *match_end = end;
1101                                 }
1102                         }
1103                         break;
1104                 }
1105         } while (gtk_text_iter_forward_line (&search));
1106
1107         g_strfreev ((gchar **) lines);
1108
1109         return retval;
1110 }
1111
1112 static const gchar *
1113 g_utf8_strrcasestr (const gchar *haystack, const gchar *needle)
1114 {
1115         gsize needle_len;
1116         gsize haystack_len;
1117         const gchar *ret = NULL;
1118         gchar *p;
1119         gchar *casefold;
1120         gchar *caseless_haystack;
1121         gint i;
1122
1123         g_return_val_if_fail (haystack != NULL, NULL);
1124         g_return_val_if_fail (needle != NULL, NULL);
1125
1126         casefold = g_utf8_casefold (haystack, -1);
1127         caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1128         g_free (casefold);
1129
1130         needle_len = g_utf8_strlen (needle, -1);
1131         haystack_len = g_utf8_strlen (caseless_haystack, -1);
1132
1133         if (needle_len == 0)
1134         {
1135                 ret = (gchar *) haystack;
1136                 goto finally_1;
1137         }
1138
1139         if (haystack_len < needle_len)
1140         {
1141                 ret = NULL;
1142                 goto finally_1;
1143         }
1144
1145         i = haystack_len - needle_len;
1146         p = g_utf8_offset_to_pointer (caseless_haystack, i);
1147         needle_len = strlen (needle);
1148
1149         while (p >= caseless_haystack)
1150         {
1151                 if (strncmp (p, needle, needle_len) == 0)
1152                 {
1153                         ret = pointer_from_offset_skipping_decomp (haystack, i);
1154                         goto finally_1;
1155                 }
1156
1157                 p = g_utf8_prev_char (p);
1158                 i--;
1159         }
1160
1161 finally_1:
1162         g_free (caseless_haystack);
1163
1164         return ret;
1165 }
1166
1167 static gboolean
1168 backward_lines_match (const GtkTextIter *start,
1169                       const gchar      **lines,
1170                       gboolean           visible_only,
1171                       gboolean           slice,
1172                       GtkTextIter       *match_start,
1173                       GtkTextIter       *match_end)
1174 {
1175         GtkTextIter line, next;
1176         gchar *line_text;
1177         const gchar *found;
1178         gint offset;
1179
1180         if (*lines == NULL || **lines == '\0')
1181         {
1182                 if (match_start)
1183                         *match_start = *start;
1184                 if (match_end)
1185                         *match_end = *start;
1186                 return TRUE;
1187         }
1188
1189         line = next = *start;
1190         if (gtk_text_iter_get_line_offset (&next) == 0)
1191         {
1192                 if (!gtk_text_iter_backward_line (&next))
1193                         return FALSE;
1194         }
1195         else
1196                 gtk_text_iter_set_line_offset (&next, 0);
1197
1198         if (slice)
1199         {
1200                 if (visible_only)
1201                         line_text = gtk_text_iter_get_visible_slice (&next, &line);
1202                 else
1203                         line_text = gtk_text_iter_get_slice (&next, &line);
1204         }
1205         else
1206         {
1207                 if (visible_only)
1208                         line_text = gtk_text_iter_get_visible_text (&next, &line);
1209                 else
1210                         line_text = gtk_text_iter_get_text (&next, &line);
1211         }
1212
1213         if (match_start) /* if this is the first line we're matching */
1214         {
1215                 found = g_utf8_strrcasestr (line_text, *lines);
1216         }
1217         else
1218         {
1219                 /* If it's not the first line, we have to match from the
1220                  * start of the line.
1221                  */
1222                 if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
1223                                            strlen (*lines)))
1224                         found = line_text;
1225                 else
1226                         found = NULL;
1227         }
1228
1229         if (found == NULL)
1230         {
1231                 g_free (line_text);
1232                 return FALSE;
1233         }
1234
1235         /* Get offset to start of search string */
1236         offset = g_utf8_strlen (line_text, found - line_text);
1237
1238         forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
1239
1240         /* If match start needs to be returned, set it to the
1241          * start of the search string.
1242          */
1243         if (match_start)
1244         {
1245                 *match_start = next;
1246         }
1247
1248         /* Go to end of search string */
1249         forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
1250
1251         g_free (line_text);
1252
1253         ++lines;
1254
1255         if (match_end)
1256                 *match_end = next;
1257
1258         /* try to match the rest of the lines forward, passing NULL
1259          * for match_start so lines_match will try to match the entire
1260          * line */
1261         return lines_match (&next, lines, visible_only,
1262                             slice, NULL, match_end);
1263 }
1264
1265 gboolean
1266 empathy_text_iter_backward_search (const GtkTextIter   *iter,
1267                                   const gchar         *str,
1268                                   GtkTextIter         *match_start,
1269                                   GtkTextIter         *match_end,
1270                                   const GtkTextIter   *limit)
1271 {
1272         gchar **lines = NULL;
1273         GtkTextIter match;
1274         gboolean retval = FALSE;
1275         GtkTextIter search;
1276         gboolean visible_only;
1277         gboolean slice;
1278
1279         g_return_val_if_fail (iter != NULL, FALSE);
1280         g_return_val_if_fail (str != NULL, FALSE);
1281
1282         if (limit && gtk_text_iter_compare (iter, limit) <= 0)
1283                 return FALSE;
1284
1285         if (*str == '\0')
1286         {
1287                 /* If we can move one char, return the empty string there */
1288                 match = *iter;
1289
1290                 if (gtk_text_iter_backward_char (&match))
1291                 {
1292                         if (limit && gtk_text_iter_equal (&match, limit))
1293                                 return FALSE;
1294
1295                         if (match_start)
1296                                 *match_start = match;
1297                         if (match_end)
1298                                 *match_end = match;
1299                         return TRUE;
1300                 }
1301                 else
1302                 {
1303                         return FALSE;
1304                 }
1305         }
1306
1307         visible_only = TRUE;
1308         slice = TRUE;
1309
1310         /* locate all lines */
1311         lines = strbreakup (str, "\n", -1);
1312
1313         search = *iter;
1314
1315         while (TRUE)
1316         {
1317                 /* This loop has an inefficient worst-case, where
1318                  * gtk_text_iter_get_text () is called repeatedly on
1319                  * a single line.
1320                  */
1321                 GtkTextIter end;
1322
1323                 if (limit && gtk_text_iter_compare (&search, limit) <= 0)
1324                         break;
1325
1326                 if (backward_lines_match (&search, (const gchar**)lines,
1327                                           visible_only, slice, &match, &end))
1328                 {
1329                         if (limit == NULL || (limit &&
1330                                               gtk_text_iter_compare (&end, limit) > 0))
1331                         {
1332                                 retval = TRUE;
1333
1334                                 if (match_start)
1335                                         *match_start = match;
1336                                 if (match_end)
1337                                         *match_end = end;
1338                         }
1339                         break;
1340                 }
1341
1342                 if (gtk_text_iter_get_line_offset (&search) == 0)
1343                 {
1344                         if (!gtk_text_iter_backward_line (&search))
1345                                 break;
1346                 }
1347                 else
1348                 {
1349                         gtk_text_iter_set_line_offset (&search, 0);
1350                 }
1351         }
1352
1353         g_strfreev ((gchar **) lines);
1354
1355         return retval;
1356 }
1357
1358 gboolean
1359 empathy_window_get_is_visible (GtkWindow *window)
1360 {
1361         GdkWindowState  state;
1362         GdkWindow      *gdk_window;
1363
1364         g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
1365
1366         gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1367         if (!gdk_window) {
1368                 return FALSE;
1369         }
1370
1371         state = gdk_window_get_state (gdk_window);
1372         if (state & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED)) {
1373                 return FALSE;
1374         }
1375
1376         return TRUE;
1377 }
1378
1379 void
1380 empathy_window_iconify (GtkWindow *window, GtkStatusIcon *status_icon)
1381 {
1382         GdkRectangle  icon_location;
1383         gulong        data[4];
1384         Display      *dpy;
1385         GdkWindow    *gdk_window;
1386
1387         gtk_status_icon_get_geometry (status_icon, NULL, &icon_location, NULL);
1388         gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1389         dpy = gdk_x11_drawable_get_xdisplay (gdk_window);
1390
1391         data[0] = icon_location.x;
1392         data[1] = icon_location.y;
1393         data[2] = icon_location.width;
1394         data[3] = icon_location.height;
1395
1396         XChangeProperty (dpy,
1397                          GDK_WINDOW_XID (gdk_window),
1398                          gdk_x11_get_xatom_by_name_for_display (gdk_drawable_get_display (gdk_window),
1399                          "_NET_WM_ICON_GEOMETRY"),
1400                          XA_CARDINAL, 32, PropModeReplace,
1401                          (guchar *)&data, 4);
1402
1403         gtk_window_set_skip_taskbar_hint (window, TRUE);
1404         gtk_window_iconify (window);
1405 }
1406
1407 /* Takes care of moving the window to the current workspace. */
1408 void
1409 empathy_window_present (GtkWindow *window,
1410                         gboolean   steal_focus)
1411 {
1412         guint32 timestamp;
1413         GdkWindow *gdk_window;
1414
1415         g_return_if_fail (GTK_IS_WINDOW (window));
1416
1417         /* Move the window to the current workspace before trying to show it.
1418          * This is the behaviour people expect when clicking on the statusbar icon. */
1419         gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1420         if (gdk_window) {
1421                 gint x, y;
1422                 gint w, h;
1423
1424                 /* Has no effect if the WM has viewports, like compiz */
1425                 gdk_x11_window_move_to_current_desktop (gdk_window);
1426
1427                 /* If window is still off-screen, hide it to force it to
1428                  * reposition on the current workspace. */
1429                 gtk_window_get_position (window, &x, &y);
1430                 gtk_window_get_size (window, &w, &h);
1431                 if (!EMPATHY_RECT_IS_ON_SCREEN (x, y, w, h))
1432                         gtk_widget_hide (GTK_WIDGET (window));
1433         }
1434
1435         timestamp = gtk_get_current_event_time ();
1436         gtk_window_present_with_time (window, timestamp);
1437         gtk_window_set_skip_taskbar_hint (window, FALSE);
1438         gtk_window_deiconify (window);
1439 }
1440
1441 GtkWindow *
1442 empathy_get_toplevel_window (GtkWidget *widget)
1443 {
1444         GtkWidget *toplevel;
1445
1446         g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
1447
1448         toplevel = gtk_widget_get_toplevel (widget);
1449         if (GTK_IS_WINDOW (toplevel) &&
1450             gtk_widget_is_toplevel (toplevel)) {
1451                 return GTK_WINDOW (toplevel);
1452         }
1453
1454         return NULL;
1455 }
1456
1457 /** empathy_make_absolute_url_len:
1458  * @url: an url
1459  * @len: a length
1460  *
1461  * Same as #empathy_make_absolute_url but for a limited string length
1462  */
1463 gchar *
1464 empathy_make_absolute_url_len (const gchar *url,
1465                                guint len)
1466 {
1467         g_return_val_if_fail (url != NULL, NULL);
1468
1469         if (g_str_has_prefix (url, "ghelp:") ||
1470             g_str_has_prefix (url, "mailto:") ||
1471             strstr (url, ":/")) {
1472                 return g_strndup (url, len);
1473         }
1474
1475         if (strstr (url, "@")) {
1476                 return g_strdup_printf ("mailto:%.*s", len, url);
1477         }
1478
1479         return g_strdup_printf ("http://%.*s", len, url);
1480 }
1481
1482 /** empathy_make_absolute_url:
1483  * @url: an url
1484  *
1485  * The URL opening code can't handle schemeless strings, so we try to be
1486  * smart and add http if there is no scheme or doesn't look like a mail
1487  * address. This should work in most cases, and let us click on strings
1488  * like "www.gnome.org".
1489  *
1490  * Returns: a newly allocated url with proper mailto: or http:// prefix, use
1491  * g_free when your are done with it
1492  */
1493 gchar *
1494 empathy_make_absolute_url (const gchar *url)
1495 {
1496         return empathy_make_absolute_url_len (url, strlen (url));
1497 }
1498
1499 void
1500 empathy_url_show (GtkWidget *parent,
1501                   const char *url)
1502 {
1503         gchar  *real_url;
1504         GError *error = NULL;
1505
1506         g_return_if_fail (parent == NULL || GTK_IS_WIDGET (parent));
1507         g_return_if_fail (url != NULL);
1508
1509         real_url = empathy_make_absolute_url (url);
1510
1511         gtk_show_uri (parent ? gtk_widget_get_screen (parent) : NULL, real_url,
1512                       gtk_get_current_event_time (), &error);
1513
1514         if (error) {
1515                 GtkWidget *dialog;
1516
1517                 dialog = gtk_message_dialog_new (NULL, 0,
1518                                                  GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
1519                                                  _("Unable to open URI"));
1520                 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1521                                                           "%s", error->message);
1522
1523                 g_signal_connect (dialog, "response",
1524                                   G_CALLBACK (gtk_widget_destroy),
1525                                   NULL);
1526                 gtk_window_present (GTK_WINDOW (dialog));
1527
1528                 g_clear_error (&error);
1529         }
1530
1531         g_free (real_url);
1532 }
1533
1534 static void
1535 link_button_hook (GtkLinkButton *button,
1536                   const gchar *link,
1537                   gpointer user_data)
1538 {
1539         empathy_url_show (GTK_WIDGET (button), link);
1540 }
1541
1542 GtkWidget *
1543 empathy_link_button_new (const gchar *url,
1544                         const gchar *title)
1545 {
1546         static gboolean hook = FALSE;
1547
1548         if (!hook) {
1549                 hook = TRUE;
1550                 gtk_link_button_set_uri_hook (link_button_hook, NULL, NULL);
1551         }
1552
1553         return gtk_link_button_new_with_label (url, title);
1554 }
1555
1556 void
1557 empathy_toggle_button_set_state_quietly (GtkWidget *widget,
1558                                         GCallback  callback,
1559                                         gpointer   user_data,
1560                                         gboolean   active)
1561 {
1562         g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
1563
1564         g_signal_handlers_block_by_func (widget, callback, user_data);
1565         g_object_set (widget, "active", active, NULL);
1566         g_signal_handlers_unblock_by_func (widget, callback, user_data);
1567 }
1568
1569 void
1570 empathy_send_file (EmpathyContact *contact, GFile *file)
1571 {
1572         EmpathyFTFactory *factory;
1573         GtkRecentManager *manager;
1574         gchar *uri;
1575
1576         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1577         g_return_if_fail (G_IS_FILE (file));
1578
1579         factory = empathy_ft_factory_dup_singleton ();
1580
1581         empathy_ft_factory_new_transfer_outgoing (factory, contact, file);
1582
1583         uri = g_file_get_uri (file);
1584         manager = gtk_recent_manager_get_default ();
1585         gtk_recent_manager_add_item (manager, uri);
1586         g_free (uri);
1587
1588         g_object_unref (factory);
1589 }
1590
1591 void
1592 empathy_send_file_from_uri_list (EmpathyContact *contact, const gchar *uri_list)
1593 {
1594         const gchar *nl;
1595         GFile *file;
1596
1597         /* Only handle a single file for now.  It would be wicked cool to be
1598            able to do multiple files, offering to zip them or whatever like
1599            nautilus-sendto does.  Note that text/uri-list is defined to have
1600            each line terminated by \r\n, but we can be tolerant of applications
1601            that only use \n or don't terminate single-line entries.
1602         */
1603         nl = strstr (uri_list, "\r\n");
1604         if (!nl) {
1605                 nl = strchr (uri_list, '\n');
1606         }
1607         if (nl) {
1608                 gchar *uri = g_strndup (uri_list, nl - uri_list);
1609                 file = g_file_new_for_uri (uri);
1610                 g_free (uri);
1611         }
1612         else {
1613                 file = g_file_new_for_uri (uri_list);
1614         }
1615
1616         empathy_send_file (contact, file);
1617
1618         g_object_unref (file);
1619 }
1620
1621 static void
1622 file_manager_send_file_response_cb (GtkDialog      *widget,
1623                                     gint            response_id,
1624                                     EmpathyContact *contact)
1625 {
1626         GFile *file;
1627
1628         if (response_id == GTK_RESPONSE_OK) {
1629                 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget));
1630
1631                 empathy_send_file (contact, file);
1632
1633                 g_object_unref (file);
1634         }
1635
1636         gtk_widget_destroy (GTK_WIDGET (widget));
1637 }
1638
1639 void
1640 empathy_send_file_with_file_chooser (EmpathyContact *contact)
1641 {
1642         GtkWidget               *widget;
1643         GtkWidget               *button;
1644
1645         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1646
1647         DEBUG ("Creating selection file chooser");
1648
1649         widget = gtk_file_chooser_dialog_new (_("Select a file"),
1650                                               NULL,
1651                                               GTK_FILE_CHOOSER_ACTION_OPEN,
1652                                               GTK_STOCK_CANCEL,
1653                                               GTK_RESPONSE_CANCEL,
1654                                               NULL);
1655
1656         /* send button */
1657         button = gtk_button_new_with_mnemonic (_("_Send"));
1658         gtk_button_set_image (GTK_BUTTON (button),
1659                 gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1660                                               GTK_ICON_SIZE_BUTTON));
1661         gtk_widget_show (button);
1662         gtk_dialog_add_action_widget (GTK_DIALOG (widget), button,
1663                                       GTK_RESPONSE_OK);
1664         gtk_widget_set_can_default (button, TRUE);
1665         gtk_dialog_set_default_response (GTK_DIALOG (widget),
1666                                          GTK_RESPONSE_OK);
1667
1668         gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), FALSE);
1669
1670         g_signal_connect (widget, "response",
1671                           G_CALLBACK (file_manager_send_file_response_cb),
1672                           contact);
1673
1674         gtk_widget_show (widget);
1675 }
1676
1677 static void
1678 file_manager_receive_file_response_cb (GtkDialog *dialog,
1679                                        GtkResponseType response,
1680                                        EmpathyFTHandler *handler)
1681 {
1682         EmpathyFTFactory *factory;
1683         GFile *file;
1684
1685         if (response == GTK_RESPONSE_OK) {
1686                 factory = empathy_ft_factory_dup_singleton ();
1687                 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
1688
1689                 empathy_ft_factory_set_destination_for_incoming_handler
1690                         (factory, handler, file);
1691
1692                 g_object_unref (factory);
1693                 g_object_unref (file);
1694         } else {
1695                 /* unref the handler, as we dismissed the file chooser,
1696                  * and refused the transfer.
1697                  */
1698                 g_object_unref (handler);
1699         }
1700
1701         gtk_widget_destroy (GTK_WIDGET (dialog));
1702 }
1703
1704 void
1705 empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
1706 {
1707         GtkWidget *widget;
1708
1709         widget = gtk_file_chooser_dialog_new (_("Select a destination"),
1710                                               NULL,
1711                                               GTK_FILE_CHOOSER_ACTION_SAVE,
1712                                               GTK_STOCK_CANCEL,
1713                                               GTK_RESPONSE_CANCEL,
1714                                               GTK_STOCK_SAVE,
1715                                               GTK_RESPONSE_OK,
1716                                               NULL);
1717         gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget),
1718                 empathy_ft_handler_get_filename (handler));
1719         gtk_file_chooser_set_do_overwrite_confirmation
1720                 (GTK_FILE_CHOOSER (widget), TRUE);
1721
1722         g_signal_connect (widget, "response",
1723                 G_CALLBACK (file_manager_receive_file_response_cb), handler);
1724
1725         gtk_widget_show (widget);
1726 }
1727
1728 void
1729 empathy_string_parser_substr (const gchar *text,
1730                               gssize len,
1731                               EmpathyStringParser *parsers,
1732                               gpointer user_data)
1733 {
1734         if (parsers != NULL && parsers[0].match_func != NULL) {
1735                 parsers[0].match_func (text, len,
1736                                        parsers[0].replace_func, parsers + 1,
1737                                        user_data);
1738         }
1739 }
1740
1741 void
1742 empathy_string_match_link (const gchar *text,
1743                            gssize len,
1744                            EmpathyStringReplace replace_func,
1745                            EmpathyStringParser *sub_parsers,
1746                            gpointer user_data)
1747 {
1748         GRegex     *uri_regex;
1749         GMatchInfo *match_info;
1750         gboolean    match;
1751         gint        last = 0;
1752
1753         uri_regex = empathy_uri_regex_dup_singleton ();
1754         match = g_regex_match_full (uri_regex, text, len, 0, 0, &match_info, NULL);
1755         if (match) {
1756                 gint s = 0, e = 0;
1757
1758                 do {
1759                         g_match_info_fetch_pos (match_info, 0, &s, &e);
1760
1761                         if (s > last) {
1762                                 /* Append the text between last link (or the
1763                                  * start of the message) and this link */
1764                                 empathy_string_parser_substr (text + last,
1765                                                               s - last,
1766                                                               sub_parsers,
1767                                                               user_data);
1768                         }
1769
1770                         replace_func (text + s, e - s, NULL, user_data);
1771
1772                         last = e;
1773                 } while (g_match_info_next (match_info, NULL));
1774         }
1775
1776         empathy_string_parser_substr (text + last, len - last,
1777                                       sub_parsers, user_data);
1778
1779         g_match_info_free (match_info);
1780         g_regex_unref (uri_regex);
1781 }
1782
1783 void
1784 empathy_string_match_smiley (const gchar *text,
1785                              gssize len,
1786                              EmpathyStringReplace replace_func,
1787                              EmpathyStringParser *sub_parsers,
1788                              gpointer user_data)
1789 {
1790         guint last = 0;
1791         EmpathySmileyManager *smiley_manager;
1792         GSList *hits, *l;
1793
1794         smiley_manager = empathy_smiley_manager_dup_singleton ();
1795         hits = empathy_smiley_manager_parse_len (smiley_manager, text, len);
1796
1797         for (l = hits; l; l = l->next) {
1798                 EmpathySmileyHit *hit = l->data;
1799
1800                 if (hit->start > last) {
1801                         /* Append the text between last smiley (or the
1802                          * start of the message) and this smiley */
1803                         empathy_string_parser_substr (text + last,
1804                                                       hit->start - last,
1805                                                       sub_parsers, user_data);
1806                 }
1807
1808                 replace_func (text + hit->start, hit->end - hit->start,
1809                               hit, user_data);
1810
1811                 last = hit->end;
1812
1813                 empathy_smiley_hit_free (hit);
1814         }
1815         g_slist_free (hits);
1816         g_object_unref (smiley_manager);
1817
1818         empathy_string_parser_substr (text + last, len - last,
1819                                       sub_parsers, user_data);
1820 }
1821
1822 void
1823 empathy_string_match_all (const gchar *text,
1824                           gssize len,
1825                           EmpathyStringReplace replace_func,
1826                           EmpathyStringParser *sub_parsers,
1827                           gpointer user_data)
1828 {
1829         replace_func (text, len, NULL, user_data);
1830 }
1831