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