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