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