]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-ui-utils.c
Updated Kannada translation
[empathy.git] / libempathy-gtk / empathy-ui-utils.c
1 /*
2  * Copyright (C) 2002-2007 Imendio AB
3  * Copyright (C) 2007-2010 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
25  *          Travis Reitter <travis.reitter@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 #include "empathy-ui-utils.h"
34
35 #include <X11/Xatom.h>
36 #include <gdk/gdkx.h>
37 #include <glib/gi18n-lib.h>
38 #include <gio/gdesktopappinfo.h>
39 #include <tp-account-widgets/tpaw-live-search.h>
40 #include <tp-account-widgets/tpaw-pixbuf-utils.h>
41 #include <tp-account-widgets/tpaw-utils.h>
42
43 #include "empathy-ft-factory.h"
44 #include "empathy-images.h"
45 #include "empathy-utils.h"
46
47 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
48 #include "empathy-debug.h"
49
50 void
51 empathy_gtk_init (void)
52 {
53   static gboolean initialized = FALSE;
54
55   if (initialized)
56     return;
57
58   empathy_init ();
59
60   gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
61       PKGDATADIR G_DIR_SEPARATOR_S "icons");
62
63   /* Add icons from source dir if available */
64   if (g_getenv ("EMPATHY_SRCDIR") != NULL)
65     {
66       gchar *path;
67
68       path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "data",
69           "icons", "local-copy", NULL);
70
71       if (g_file_test (path, G_FILE_TEST_EXISTS))
72         gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), path);
73
74       g_free (path);
75     }
76
77   initialized = TRUE;
78 }
79
80 const gchar *
81 empathy_icon_name_for_presence (TpConnectionPresenceType presence)
82 {
83   switch (presence)
84     {
85       case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
86         return EMPATHY_IMAGE_AVAILABLE;
87       case TP_CONNECTION_PRESENCE_TYPE_BUSY:
88         return EMPATHY_IMAGE_BUSY;
89       case TP_CONNECTION_PRESENCE_TYPE_AWAY:
90         return EMPATHY_IMAGE_AWAY;
91       case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
92         if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
93                    EMPATHY_IMAGE_EXT_AWAY))
94           return EMPATHY_IMAGE_EXT_AWAY;
95
96         /* The 'extended-away' icon is not an official one so we fallback to
97          * idle if it's not implemented */
98         return EMPATHY_IMAGE_IDLE;
99       case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
100         if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
101                    EMPATHY_IMAGE_HIDDEN))
102           return EMPATHY_IMAGE_HIDDEN;
103
104         /* The 'hidden' icon is not an official one so we fallback to offline if
105          * it's not implemented */
106         return EMPATHY_IMAGE_OFFLINE;
107       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
108       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
109         return EMPATHY_IMAGE_OFFLINE;
110       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
111         return EMPATHY_IMAGE_PENDING;
112       case TP_CONNECTION_PRESENCE_TYPE_UNSET:
113       default:
114         return NULL;
115     }
116
117   return NULL;
118 }
119
120 const gchar *
121 empathy_icon_name_for_contact (EmpathyContact *contact)
122 {
123   TpConnectionPresenceType presence;
124
125   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact),
126       EMPATHY_IMAGE_OFFLINE);
127
128   presence = empathy_contact_get_presence (contact);
129   return empathy_icon_name_for_presence (presence);
130 }
131
132 const gchar *
133 empathy_icon_name_for_individual (FolksIndividual *individual)
134 {
135   FolksPresenceType folks_presence;
136   TpConnectionPresenceType presence;
137
138   folks_presence = folks_presence_details_get_presence_type (
139       FOLKS_PRESENCE_DETAILS (individual));
140   presence = empathy_folks_presence_type_to_tp (folks_presence);
141
142   return empathy_icon_name_for_presence (presence);
143 }
144
145 const gchar *
146 empathy_protocol_name_for_contact (EmpathyContact *contact)
147 {
148   TpAccount *account;
149
150   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
151
152   account = empathy_contact_get_account (contact);
153   if (account == NULL)
154     return NULL;
155
156   return tp_account_get_icon_name (account);
157 }
158
159 struct SizeData
160 {
161   gint width;
162   gint height;
163   gboolean preserve_aspect_ratio;
164 };
165
166 static void
167 pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
168     int width,
169     int height,
170     struct SizeData *data)
171 {
172   g_return_if_fail (width > 0 && height > 0);
173
174   if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0))
175     {
176       if (data->width < 0)
177         {
178           width = width * (double) data->height / (gdouble) height;
179           height = data->height;
180         }
181       else if (data->height < 0)
182         {
183           height = height * (double) data->width / (double) width;
184           width = data->width;
185         }
186       else if ((double) height * (double) data->width >
187            (double) width * (double) data->height)
188         {
189           width = 0.5 + (double) width * (double) data->height / (double) height;
190           height = data->height;
191         }
192       else
193         {
194           height = 0.5 + (double) height * (double) data->width / (double) width;
195           width = data->width;
196         }
197     }
198   else
199     {
200       if (data->width > 0)
201         width = data->width;
202
203       if (data->height > 0)
204         height = data->height;
205     }
206
207   gdk_pixbuf_loader_set_size (loader, width, height);
208 }
209
210 static void
211 empathy_avatar_pixbuf_roundify (GdkPixbuf *pixbuf)
212 {
213   gint width, height, rowstride;
214   guchar *pixels;
215
216   width = gdk_pixbuf_get_width (pixbuf);
217   height = gdk_pixbuf_get_height (pixbuf);
218   rowstride = gdk_pixbuf_get_rowstride (pixbuf);
219   pixels = gdk_pixbuf_get_pixels (pixbuf);
220
221   if (width < 6 || height < 6)
222     return;
223
224   /* Top left */
225   pixels[3] = 0;
226   pixels[7] = 0x80;
227   pixels[11] = 0xC0;
228   pixels[rowstride + 3] = 0x80;
229   pixels[rowstride * 2 + 3] = 0xC0;
230
231   /* Top right */
232   pixels[width * 4 - 1] = 0;
233   pixels[width * 4 - 5] = 0x80;
234   pixels[width * 4 - 9] = 0xC0;
235   pixels[rowstride + (width * 4) - 1] = 0x80;
236   pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
237
238   /* Bottom left */
239   pixels[(height - 1) * rowstride + 3] = 0;
240   pixels[(height - 1) * rowstride + 7] = 0x80;
241   pixels[(height - 1) * rowstride + 11] = 0xC0;
242   pixels[(height - 2) * rowstride + 3] = 0x80;
243   pixels[(height - 3) * rowstride + 3] = 0xC0;
244
245   /* Bottom right */
246   pixels[height * rowstride - 1] = 0;
247   pixels[(height - 1) * rowstride - 1] = 0x80;
248   pixels[(height - 2) * rowstride - 1] = 0xC0;
249   pixels[height * rowstride - 5] = 0x80;
250   pixels[height * rowstride - 9] = 0xC0;
251 }
252
253 static gboolean
254 empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
255 {
256   gint height, rowstride, i;
257   guchar *pixels;
258   guchar *row;
259
260   height = gdk_pixbuf_get_height (pixbuf);
261   rowstride = gdk_pixbuf_get_rowstride (pixbuf);
262   pixels = gdk_pixbuf_get_pixels (pixbuf);
263
264   row = pixels;
265   for (i = 3; i < rowstride; i+=4)
266     if (row[i] < 0xfe)
267       return FALSE;
268
269   for (i = 1; i < height - 1; i++)
270     {
271       row = pixels + (i*rowstride);
272       if (row[3] < 0xfe || row[rowstride-1] < 0xfe)
273         return FALSE;
274     }
275
276   row = pixels + ((height-1) * rowstride);
277   for (i = 3; i < rowstride; i+=4)
278     if (row[i] < 0xfe)
279       return FALSE;
280
281   return TRUE;
282 }
283
284 static GdkPixbuf *
285 pixbuf_round_corners (GdkPixbuf *pixbuf)
286 {
287   GdkPixbuf *result;
288
289   if (!gdk_pixbuf_get_has_alpha (pixbuf))
290     {
291       result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
292           gdk_pixbuf_get_width (pixbuf),
293           gdk_pixbuf_get_height (pixbuf));
294
295       gdk_pixbuf_copy_area (pixbuf, 0, 0,
296           gdk_pixbuf_get_width (pixbuf),
297           gdk_pixbuf_get_height (pixbuf),
298           result,
299           0, 0);
300     }
301   else
302     {
303       result = g_object_ref (pixbuf);
304     }
305
306   if (empathy_gdk_pixbuf_is_opaque (result))
307     empathy_avatar_pixbuf_roundify (result);
308
309   return result;
310 }
311
312 static GdkPixbuf *
313 avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
314 {
315   GdkPixbuf *pixbuf;
316
317   pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
318
319   return pixbuf_round_corners (pixbuf);
320 }
321
322 static GdkPixbuf *
323 empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
324     gint width,
325     gint height)
326 {
327   GdkPixbuf *pixbuf;
328   GdkPixbufLoader *loader;
329   struct SizeData data;
330   GError *error = NULL;
331
332   if (!avatar)
333     return NULL;
334
335   data.width = width;
336   data.height = height;
337   data.preserve_aspect_ratio = TRUE;
338
339   loader = gdk_pixbuf_loader_new ();
340
341   g_signal_connect (loader, "size-prepared",
342       G_CALLBACK (pixbuf_from_avatar_size_prepared_cb), &data);
343
344   if (avatar->len == 0)
345     {
346       g_warning ("Avatar has 0 length");
347       return NULL;
348     }
349   else if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error))
350     {
351       g_warning ("Couldn't write avatar image:%p with "
352           "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
353           avatar->data, avatar->len, error->message);
354
355       g_error_free (error);
356       return NULL;
357     }
358
359   gdk_pixbuf_loader_close (loader, NULL);
360   pixbuf = avatar_pixbuf_from_loader (loader);
361
362   g_object_unref (loader);
363
364   return pixbuf;
365 }
366
367 GdkPixbuf *
368 empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact *contact,
369     gint width,
370     gint height)
371 {
372   EmpathyAvatar *avatar;
373
374   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
375
376   avatar = empathy_contact_get_avatar (contact);
377
378   return empathy_pixbuf_from_avatar_scaled (avatar, width, height);
379 }
380
381 typedef struct
382 {
383   GSimpleAsyncResult *result;
384   guint width;
385   guint height;
386   GCancellable *cancellable;
387 } PixbufAvatarFromIndividualClosure;
388
389 static PixbufAvatarFromIndividualClosure *
390 pixbuf_avatar_from_individual_closure_new (FolksIndividual *individual,
391     GSimpleAsyncResult *result,
392     gint width,
393     gint height,
394     GCancellable *cancellable)
395 {
396   PixbufAvatarFromIndividualClosure *closure;
397
398   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
399   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
400
401   closure = g_slice_new0 (PixbufAvatarFromIndividualClosure);
402   closure->result = g_object_ref (result);
403   closure->width = width;
404   closure->height = height;
405
406   if (cancellable != NULL)
407     closure->cancellable = g_object_ref (cancellable);
408
409   return closure;
410 }
411
412 static void
413 pixbuf_avatar_from_individual_closure_free (
414     PixbufAvatarFromIndividualClosure *closure)
415 {
416   g_clear_object (&closure->cancellable);
417   g_object_unref (closure->result);
418   g_slice_free (PixbufAvatarFromIndividualClosure, closure);
419 }
420
421 /**
422  * @pixbuf: (transfer all)
423  *
424  * Return: (transfer all)
425  */
426 static GdkPixbuf *
427 transform_pixbuf (GdkPixbuf *pixbuf)
428 {
429   GdkPixbuf *result;
430
431   result = pixbuf_round_corners (pixbuf);
432   g_object_unref (pixbuf);
433
434   return result;
435 }
436
437 static void
438 avatar_icon_load_cb (GObject *object,
439     GAsyncResult *result,
440     gpointer user_data)
441 {
442   GLoadableIcon *icon = G_LOADABLE_ICON (object);
443   PixbufAvatarFromIndividualClosure *closure = user_data;
444   GInputStream *stream;
445   GError *error = NULL;
446   GdkPixbuf *pixbuf;
447   GdkPixbuf *final_pixbuf;
448
449   stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
450   if (error != NULL)
451     {
452       DEBUG ("Failed to open avatar stream: %s", error->message);
453       g_simple_async_result_set_from_error (closure->result, error);
454       goto out;
455     }
456
457   pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream,
458       closure->width, closure->height, TRUE, closure->cancellable, &error);
459
460   g_object_unref (stream);
461
462   if (pixbuf == NULL)
463     {
464       DEBUG ("Failed to read avatar: %s", error->message);
465       g_simple_async_result_set_from_error (closure->result, error);
466       goto out;
467     }
468
469   final_pixbuf = transform_pixbuf (pixbuf);
470
471   /* Pass ownership of final_pixbuf to the result */
472   g_simple_async_result_set_op_res_gpointer (closure->result,
473       final_pixbuf, g_object_unref);
474
475 out:
476   g_simple_async_result_complete (closure->result);
477
478   g_clear_error (&error);
479   pixbuf_avatar_from_individual_closure_free (closure);
480 }
481
482 void
483 empathy_pixbuf_avatar_from_individual_scaled_async (
484     FolksIndividual *individual,
485     gint width,
486     gint height,
487     GCancellable *cancellable,
488     GAsyncReadyCallback callback,
489     gpointer user_data)
490 {
491   GLoadableIcon *avatar_icon;
492   GSimpleAsyncResult *result;
493   PixbufAvatarFromIndividualClosure *closure;
494
495   result = g_simple_async_result_new (G_OBJECT (individual),
496       callback, user_data, empathy_pixbuf_avatar_from_individual_scaled_async);
497
498   avatar_icon = folks_avatar_details_get_avatar (
499       FOLKS_AVATAR_DETAILS (individual));
500
501   if (avatar_icon == NULL)
502     {
503       g_simple_async_result_set_error (result, G_IO_ERROR,
504         G_IO_ERROR_NOT_FOUND, "no avatar found");
505
506       g_simple_async_result_complete (result);
507       g_object_unref (result);
508       return;
509     }
510
511   closure = pixbuf_avatar_from_individual_closure_new (individual, result,
512       width, height, cancellable);
513
514   g_return_if_fail (closure != NULL);
515
516   g_loadable_icon_load_async (avatar_icon, width, cancellable,
517       avatar_icon_load_cb, closure);
518
519   g_object_unref (result);
520 }
521
522 /* Return a ref on the GdkPixbuf */
523 GdkPixbuf *
524 empathy_pixbuf_avatar_from_individual_scaled_finish (
525     FolksIndividual *individual,
526     GAsyncResult *result,
527     GError **error)
528 {
529   GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
530   gboolean result_valid;
531   GdkPixbuf *pixbuf;
532
533   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
534   g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
535
536   if (g_simple_async_result_propagate_error (simple, error))
537     return NULL;
538
539   result_valid = g_simple_async_result_is_valid (result,
540       G_OBJECT (individual),
541       empathy_pixbuf_avatar_from_individual_scaled_async);
542
543   g_return_val_if_fail (result_valid, NULL);
544
545   pixbuf = g_simple_async_result_get_op_res_gpointer (simple);
546   return pixbuf != NULL ? g_object_ref (pixbuf) : NULL;
547 }
548
549 GdkPixbuf *
550 empathy_pixbuf_contact_status_icon (EmpathyContact *contact,
551     gboolean show_protocol)
552 {
553   const gchar *icon_name;
554
555   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
556
557   icon_name = empathy_icon_name_for_contact (contact);
558
559   if (icon_name == NULL)
560     return NULL;
561
562   return empathy_pixbuf_contact_status_icon_with_icon_name (contact,
563       icon_name, show_protocol);
564 }
565
566 static GdkPixbuf * empathy_pixbuf_protocol_from_contact_scaled (
567     EmpathyContact *contact,
568     gint width,
569     gint height);
570
571 GdkPixbuf *
572 empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact *contact,
573     const gchar *icon_name,
574     gboolean show_protocol)
575 {
576   GdkPixbuf *pix_status;
577   GdkPixbuf *pix_protocol;
578   gchar *icon_filename;
579   gint height, width;
580   gint numerator, denominator;
581
582   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact) ||
583       (show_protocol == FALSE), NULL);
584   g_return_val_if_fail (icon_name != NULL, NULL);
585
586   numerator = 3;
587   denominator = 4;
588
589   icon_filename = tpaw_filename_from_icon_name (icon_name,
590       GTK_ICON_SIZE_MENU);
591
592   if (icon_filename == NULL)
593     {
594       DEBUG ("icon name: %s could not be found\n", icon_name);
595       return NULL;
596     }
597
598   pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
599
600   if (pix_status == NULL)
601     {
602       DEBUG ("Could not open icon %s\n", icon_filename);
603       g_free (icon_filename);
604       return NULL;
605     }
606
607   g_free (icon_filename);
608
609   if (!show_protocol)
610     return pix_status;
611
612   height = gdk_pixbuf_get_height (pix_status);
613   width = gdk_pixbuf_get_width (pix_status);
614
615   pix_protocol = empathy_pixbuf_protocol_from_contact_scaled (contact,
616       width * numerator / denominator,
617       height * numerator / denominator);
618
619   if (pix_protocol == NULL)
620     return pix_status;
621
622   gdk_pixbuf_composite (pix_protocol, pix_status,
623       0, height - height * numerator / denominator,
624       width * numerator / denominator, height * numerator / denominator,
625       0, height - height * numerator / denominator,
626       1, 1,
627       GDK_INTERP_BILINEAR, 255);
628
629   g_object_unref (pix_protocol);
630
631   return pix_status;
632 }
633
634 static GdkPixbuf *
635 empathy_pixbuf_protocol_from_contact_scaled (EmpathyContact *contact,
636     gint width,
637     gint height)
638 {
639   TpAccount *account;
640   gchar *filename;
641   GdkPixbuf *pixbuf = NULL;
642
643   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
644
645   account = empathy_contact_get_account (contact);
646   filename = tpaw_filename_from_icon_name (
647       tp_account_get_icon_name (account), GTK_ICON_SIZE_MENU);
648
649   if (filename != NULL)
650     {
651       pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width, height, NULL);
652       g_free (filename);
653     }
654
655   return pixbuf;
656 }
657
658 void
659 empathy_url_show (GtkWidget *parent,
660       const char *url)
661 {
662   gchar  *real_url;
663   GError *error = NULL;
664
665   g_return_if_fail (parent == NULL || GTK_IS_WIDGET (parent));
666   g_return_if_fail (url != NULL);
667
668   real_url = tpaw_make_absolute_url (url);
669
670   gtk_show_uri (parent ? gtk_widget_get_screen (parent) : NULL, real_url,
671       gtk_get_current_event_time (), &error);
672
673   if (error)
674     {
675       GtkWidget *dialog;
676
677       dialog = gtk_message_dialog_new (NULL, 0,
678           GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
679           _("Unable to open URI"));
680
681       gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
682           "%s", error->message);
683
684       g_signal_connect (dialog, "response",
685             G_CALLBACK (gtk_widget_destroy), NULL);
686
687       gtk_window_present (GTK_WINDOW (dialog));
688
689       g_clear_error (&error);
690     }
691
692   g_free (real_url);
693 }
694
695 void
696 empathy_send_file (EmpathyContact *contact,
697     GFile *file)
698 {
699   EmpathyFTFactory *factory;
700   GtkRecentManager *manager;
701   gchar *uri;
702
703   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
704   g_return_if_fail (G_IS_FILE (file));
705
706   factory = empathy_ft_factory_dup_singleton ();
707
708   empathy_ft_factory_new_transfer_outgoing (factory, contact, file,
709       empathy_get_current_action_time ());
710
711   uri = g_file_get_uri (file);
712   manager = gtk_recent_manager_get_default ();
713   gtk_recent_manager_add_item (manager, uri);
714   g_free (uri);
715
716   g_object_unref (factory);
717 }
718
719 void
720 empathy_send_file_from_uri_list (EmpathyContact *contact,
721     const gchar *uri_list)
722 {
723   const gchar *nl;
724   GFile *file;
725
726   /* Only handle a single file for now. It would be wicked cool to be
727      able to do multiple files, offering to zip them or whatever like
728      nautilus-sendto does. Note that text/uri-list is defined to have
729      each line terminated by \r\n, but we can be tolerant of applications
730      that only use \n or don't terminate single-line entries.
731   */
732   nl = strstr (uri_list, "\r\n");
733   if (!nl)
734     nl = strchr (uri_list, '\n');
735
736   if (nl)
737     {
738       gchar *uri = g_strndup (uri_list, nl - uri_list);
739       file = g_file_new_for_uri (uri);
740       g_free (uri);
741     }
742   else
743     {
744       file = g_file_new_for_uri (uri_list);
745     }
746
747   empathy_send_file (contact, file);
748
749   g_object_unref (file);
750 }
751
752 static void
753 file_manager_send_file_response_cb (GtkDialog *widget,
754     gint response_id,
755     EmpathyContact *contact)
756 {
757   GFile *file;
758
759   if (response_id == GTK_RESPONSE_OK)
760     {
761       file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget));
762
763       empathy_send_file (contact, file);
764
765       g_object_unref (file);
766     }
767
768   g_object_unref (contact);
769   gtk_widget_destroy (GTK_WIDGET (widget));
770 }
771
772 static gboolean
773 filter_cb (const GtkFileFilterInfo *filter_info,
774     gpointer data)
775 {
776   /* filter out socket files */
777   return tp_strdiff (filter_info->mime_type, "inode/socket");
778 }
779
780 static GtkFileFilter *
781 create_file_filter (void)
782 {
783   GtkFileFilter *filter;
784
785   filter = gtk_file_filter_new ();
786
787   gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_MIME_TYPE, filter_cb,
788     NULL, NULL);
789
790   return filter;
791 }
792
793 void
794 empathy_send_file_with_file_chooser (EmpathyContact *contact)
795 {
796   GtkWidget *widget;
797   GtkWidget *button;
798
799   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
800
801   DEBUG ("Creating selection file chooser");
802
803   widget = gtk_file_chooser_dialog_new (_("Select a file"), NULL,
804       GTK_FILE_CHOOSER_ACTION_OPEN,
805       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
806       NULL);
807
808   /* send button */
809   button = gtk_button_new_with_mnemonic (_("_Send"));
810   gtk_button_set_image (GTK_BUTTON (button),
811       gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
812         GTK_ICON_SIZE_BUTTON));
813   gtk_widget_show (button);
814
815   gtk_dialog_add_action_widget (GTK_DIALOG (widget), button,
816       GTK_RESPONSE_OK);
817
818   gtk_widget_set_can_default (button, TRUE);
819   gtk_dialog_set_default_response (GTK_DIALOG (widget),
820       GTK_RESPONSE_OK);
821
822   gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), FALSE);
823
824   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget),
825       g_get_home_dir ());
826
827   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget),
828       create_file_filter ());
829
830   g_signal_connect (widget, "response",
831       G_CALLBACK (file_manager_send_file_response_cb), g_object_ref (contact));
832
833   gtk_widget_show (widget);
834 }
835
836 static void
837 file_manager_receive_file_response_cb (GtkDialog *dialog,
838     GtkResponseType response,
839     EmpathyFTHandler *handler)
840 {
841   EmpathyFTFactory *factory;
842   GFile *file;
843
844   if (response == GTK_RESPONSE_OK)
845     {
846       GFile *parent;
847       GFileInfo *info;
848       guint64 free_space, file_size;
849       GError *error = NULL;
850
851       file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
852       parent = g_file_get_parent (file);
853       info = g_file_query_filesystem_info (parent,
854           G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, &error);
855
856       g_object_unref (parent);
857
858       if (error != NULL)
859         {
860           g_warning ("Error: %s", error->message);
861
862           g_object_unref (file);
863           return;
864         }
865
866       free_space = g_file_info_get_attribute_uint64 (info,
867           G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
868       file_size = empathy_ft_handler_get_total_bytes (handler);
869
870       g_object_unref (info);
871
872       if (file_size > free_space)
873         {
874           GtkWidget *message = gtk_message_dialog_new (GTK_WINDOW (dialog),
875             GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
876             GTK_BUTTONS_CLOSE,
877             _("Insufficient free space to save file"));
878           char *file_size_str, *free_space_str;
879
880           file_size_str = g_format_size (file_size);
881           free_space_str = g_format_size (free_space);
882
883           gtk_message_dialog_format_secondary_text (
884               GTK_MESSAGE_DIALOG (message),
885               _("%s of free space are required to save this "
886                 "file, but only %s is available. Please "
887                 "choose another location."),
888             file_size_str, free_space_str);
889
890           gtk_dialog_run (GTK_DIALOG (message));
891
892           g_free (file_size_str);
893           g_free (free_space_str);
894           gtk_widget_destroy (message);
895
896           g_object_unref (file);
897
898           return;
899         }
900
901       factory = empathy_ft_factory_dup_singleton ();
902
903       empathy_ft_factory_set_destination_for_incoming_handler (
904           factory, handler, file);
905
906       g_object_unref (factory);
907       g_object_unref (file);
908     }
909   else
910     {
911       /* unref the handler, as we dismissed the file chooser,
912        * and refused the transfer.
913        */
914       g_object_unref (handler);
915     }
916
917   gtk_widget_destroy (GTK_WIDGET (dialog));
918 }
919
920 void
921 empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
922 {
923   GtkWidget *widget;
924   const gchar *dir;
925   EmpathyContact *contact;
926   gchar *title;
927
928   contact = empathy_ft_handler_get_contact (handler);
929   g_assert (contact != NULL);
930
931   title = g_strdup_printf (_("Incoming file from %s"),
932     empathy_contact_get_alias (contact));
933
934   widget = gtk_file_chooser_dialog_new (title,
935       NULL, GTK_FILE_CHOOSER_ACTION_SAVE,
936       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
937       GTK_STOCK_SAVE, GTK_RESPONSE_OK,
938       NULL);
939
940   gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget),
941     empathy_ft_handler_get_filename (handler));
942
943   gtk_file_chooser_set_do_overwrite_confirmation
944     (GTK_FILE_CHOOSER (widget), TRUE);
945
946   dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
947   if (dir == NULL)
948     /* Fallback to $HOME if $XDG_DOWNLOAD_DIR is not set */
949     dir = g_get_home_dir ();
950
951   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), dir);
952
953   g_signal_connect (widget, "response",
954       G_CALLBACK (file_manager_receive_file_response_cb), handler);
955
956   gtk_widget_show (widget);
957   g_free (title);
958 }
959
960 void
961 empathy_make_color_whiter (GdkRGBA *color)
962 {
963   const GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
964
965   color->red = (color->red + white.red) / 2;
966   color->green = (color->green + white.green) / 2;
967   color->blue = (color->blue + white.blue) / 2;
968 }
969
970 static void
971 menu_deactivate_cb (GtkMenu *menu,
972   gpointer user_data)
973 {
974   /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
975   g_signal_handlers_disconnect_by_func (menu,
976          menu_deactivate_cb, user_data);
977
978   gtk_menu_detach (menu);
979 }
980
981 /* Convenient function to create a GtkMenu attached to @attach_to and detach
982  * it when the menu is not displayed any more. This is useful when creating a
983  * context menu that we want to get rid as soon as it as been displayed. */
984 GtkWidget *
985 empathy_context_menu_new (GtkWidget *attach_to)
986 {
987   GtkWidget *menu;
988
989   menu = gtk_menu_new ();
990
991   gtk_menu_attach_to_widget (GTK_MENU (menu), attach_to, NULL);
992
993   /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
994    * floating ref. We can either wait that @attach_to releases its ref when
995    * it will be destroyed (when leaving Empathy most of the time) or explicitely
996    * detach the menu when it's not displayed any more.
997    * We go for the latter as we don't want to keep useless menus in memory
998    * during the whole lifetime of Empathy. */
999   g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb), NULL);
1000
1001   return menu;
1002 }
1003
1004 gint64
1005 empathy_get_current_action_time (void)
1006 {
1007   return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
1008 }
1009
1010 /* @words = tpaw_live_search_strip_utf8_string (@text);
1011  *
1012  * User has to pass both so we don't have to compute @words ourself each time
1013  * this function is called. */
1014 gboolean
1015 empathy_individual_match_string (FolksIndividual *individual,
1016     const char *text,
1017     GPtrArray *words)
1018 {
1019   const gchar *str;
1020   GeeSet *personas;
1021   GeeIterator *iter;
1022   gboolean retval = FALSE;
1023
1024   /* check alias name */
1025   str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1026
1027   if (tpaw_live_search_match_words (str, words))
1028     return TRUE;
1029
1030   personas = folks_individual_get_personas (individual);
1031
1032   /* check contact id, remove the @server.com part */
1033   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1034   while (retval == FALSE && gee_iterator_next (iter))
1035     {
1036       FolksPersona *persona = gee_iterator_get (iter);
1037       const gchar *p;
1038
1039       if (empathy_folks_persona_is_interesting (persona))
1040         {
1041           str = folks_persona_get_display_id (persona);
1042
1043           /* Accept the persona if @text is a full prefix of his ID; that allows
1044            * user to find, say, a jabber contact by typing his JID. */
1045           if (g_str_has_prefix (str, text))
1046             {
1047               retval = TRUE;
1048             }
1049           else
1050             {
1051               gchar *dup_str = NULL;
1052               gboolean visible;
1053
1054               p = strstr (str, "@");
1055               if (p != NULL)
1056                 str = dup_str = g_strndup (str, p - str);
1057
1058               visible = tpaw_live_search_match_words (str, words);
1059               g_free (dup_str);
1060               if (visible)
1061                 retval = TRUE;
1062             }
1063         }
1064       g_clear_object (&persona);
1065     }
1066   g_clear_object (&iter);
1067
1068   /* FIXME: Add more rules here, we could check phone numbers in
1069    * contact's vCard for example. */
1070   return retval;
1071 }
1072
1073 void
1074 empathy_launch_program (const gchar *dir,
1075     const gchar *name,
1076     const gchar *args)
1077 {
1078   GdkDisplay *display;
1079   GError *error = NULL;
1080   gchar *path, *cmd;
1081   GAppInfo *app_info;
1082   GdkAppLaunchContext *context = NULL;
1083
1084   /* Try to run from source directory if possible */
1085   path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
1086       name, NULL);
1087
1088   if (!g_file_test (path, G_FILE_TEST_EXISTS))
1089     {
1090       g_free (path);
1091       path = g_build_filename (dir, name, NULL);
1092     }
1093
1094   if (args != NULL)
1095     cmd = g_strconcat (path, " ", args, NULL);
1096   else
1097     cmd = g_strdup (path);
1098
1099   app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
1100   if (app_info == NULL)
1101     {
1102       DEBUG ("Failed to create app info: %s", error->message);
1103       g_error_free (error);
1104       goto out;
1105     }
1106
1107   display = gdk_display_get_default ();
1108   context = gdk_display_get_app_launch_context (display);
1109
1110   if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
1111       &error))
1112     {
1113       g_warning ("Failed to launch %s: %s", name, error->message);
1114       g_error_free (error);
1115       goto out;
1116     }
1117
1118 out:
1119   tp_clear_object (&app_info);
1120   tp_clear_object (&context);
1121   g_free (path);
1122   g_free (cmd);
1123 }
1124
1125 /* Most of the workspace manipulation code has been copied from libwnck
1126  * Copyright (C) 2001 Havoc Pennington
1127  * Copyright (C) 2005-2007 Vincent Untz
1128  */
1129 static void
1130 _wnck_activate_workspace (Screen *screen,
1131     int new_active_space,
1132     Time timestamp)
1133 {
1134   Display *display;
1135   Window root;
1136   XEvent xev;
1137
1138   display = DisplayOfScreen (screen);
1139   root = RootWindowOfScreen (screen);
1140
1141   xev.xclient.type = ClientMessage;
1142   xev.xclient.serial = 0;
1143   xev.xclient.send_event = True;
1144   xev.xclient.display = display;
1145   xev.xclient.window = root;
1146   xev.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_CURRENT_DESKTOP");
1147   xev.xclient.format = 32;
1148   xev.xclient.data.l[0] = new_active_space;
1149   xev.xclient.data.l[1] = timestamp;
1150   xev.xclient.data.l[2] = 0;
1151   xev.xclient.data.l[3] = 0;
1152   xev.xclient.data.l[4] = 0;
1153
1154   gdk_error_trap_push ();
1155   XSendEvent (display, root, False,
1156       SubstructureRedirectMask | SubstructureNotifyMask,
1157       &xev);
1158   XSync (display, False);
1159   gdk_error_trap_pop_ignored ();
1160 }
1161
1162 static gboolean
1163 _wnck_get_cardinal (Screen *screen,
1164     Window xwindow,
1165     Atom atom,
1166     int *val)
1167 {
1168   Display *display;
1169   Atom type;
1170   int format;
1171   gulong nitems;
1172   gulong bytes_after;
1173   gulong *num;
1174   int err, result;
1175
1176   display = DisplayOfScreen (screen);
1177
1178   *val = 0;
1179
1180   gdk_error_trap_push ();
1181   type = None;
1182   result = XGetWindowProperty (display, xwindow, atom,
1183       0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
1184       &bytes_after, (void *) &num);
1185   err = gdk_error_trap_pop ();
1186   if (err != Success ||
1187       result != Success)
1188     return FALSE;
1189
1190   if (type != XA_CARDINAL)
1191     {
1192       XFree (num);
1193       return FALSE;
1194     }
1195
1196   *val = *num;
1197
1198   XFree (num);
1199
1200   return TRUE;
1201 }
1202
1203 static int
1204 window_get_workspace (Screen *xscreen,
1205     Window win)
1206 {
1207   int number;
1208
1209   if (!_wnck_get_cardinal (xscreen, win,
1210         gdk_x11_get_xatom_by_name ("_NET_WM_DESKTOP"), &number))
1211     return -1;
1212
1213   return number;
1214 }
1215
1216 /* Ask X to move to the desktop on which @window currently is
1217  * and the present @window. */
1218 void
1219 empathy_move_to_window_desktop (GtkWindow *window,
1220     guint32 timestamp)
1221 {
1222   GdkScreen *screen;
1223   Screen *xscreen;
1224   GdkWindow *gdk_window;
1225   int workspace;
1226
1227   screen = gtk_window_get_screen (window);
1228   xscreen = gdk_x11_screen_get_xscreen (screen);
1229   gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1230
1231   workspace = window_get_workspace (xscreen,
1232       gdk_x11_window_get_xid (gdk_window));
1233   if (workspace == -1)
1234     goto out;
1235
1236   _wnck_activate_workspace (xscreen, workspace, timestamp);
1237
1238 out:
1239   gtk_window_present_with_time (window, timestamp);
1240 }
1241
1242 void
1243 empathy_set_css_provider (GtkWidget *widget)
1244 {
1245   GtkCssProvider *provider;
1246   gchar *filename;
1247   GError *error = NULL;
1248   GdkScreen *screen;
1249
1250   filename = empathy_file_lookup ("empathy.css", "data");
1251
1252   provider = gtk_css_provider_new ();
1253
1254   if (!gtk_css_provider_load_from_path (provider, filename, &error))
1255     {
1256       g_warning ("Failed to load css file '%s': %s", filename, error->message);
1257       g_error_free (error);
1258       goto out;
1259     }
1260
1261   if (widget != NULL)
1262     screen = gtk_widget_get_screen (widget);
1263   else
1264     screen = gdk_screen_get_default ();
1265
1266   gtk_style_context_add_provider_for_screen (screen,
1267       GTK_STYLE_PROVIDER (provider),
1268       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1269
1270 out:
1271   g_free (filename);
1272   g_object_unref (provider);
1273 }
1274
1275 static gboolean
1276 launch_app_info (GAppInfo *app_info,
1277     GError **error)
1278 {
1279   GdkAppLaunchContext *context = NULL;
1280   GdkDisplay *display;
1281   GError *err = NULL;
1282
1283   display = gdk_display_get_default ();
1284   context = gdk_display_get_app_launch_context (display);
1285
1286   if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
1287         &err))
1288     {
1289       DEBUG ("Failed to launch %s: %s",
1290           g_app_info_get_display_name (app_info), err->message);
1291       g_propagate_error (error, err);
1292       return FALSE;
1293     }
1294
1295   tp_clear_object (&context);
1296   return TRUE;
1297 }
1298
1299 gboolean
1300 empathy_launch_external_app (const gchar *desktop_file,
1301     const gchar *args,
1302     GError **error)
1303 {
1304   GDesktopAppInfo *desktop_info;
1305   gboolean result;
1306   GError *err = NULL;
1307
1308   desktop_info = g_desktop_app_info_new (desktop_file);
1309   if (desktop_info == NULL)
1310     {
1311       DEBUG ("%s not found", desktop_file);
1312
1313       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1314           "%s not found", desktop_file);
1315       return FALSE;
1316     }
1317
1318   if (args == NULL)
1319     {
1320       result = launch_app_info (G_APP_INFO (desktop_info), error);
1321     }
1322   else
1323     {
1324       gchar *cmd;
1325       GAppInfo *app_info;
1326
1327       /* glib doesn't have API to start a desktop file with args... (#637875) */
1328       cmd = g_strdup_printf ("%s %s", g_app_info_get_commandline (
1329             (GAppInfo *) desktop_info), args);
1330
1331       app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &err);
1332       if (app_info == NULL)
1333         {
1334           DEBUG ("Failed to launch '%s': %s", cmd, err->message);
1335           g_free (cmd);
1336           g_object_unref (desktop_info);
1337           g_propagate_error (error, err);
1338           return FALSE;
1339         }
1340
1341       result = launch_app_info (app_info, error);
1342
1343       g_object_unref (app_info);
1344       g_free (cmd);
1345     }
1346
1347   g_object_unref (desktop_info);
1348   return result;
1349 }