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