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