1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2002-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
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.
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.
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
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>
28 * Part of this file is copied from GtkSourceView (gtksourceiter.c):
36 #include <X11/Xatom.h>
38 #include <glib/gi18n-lib.h>
42 #include <telepathy-glib/util.h>
43 #include <folks/folks.h>
45 #include "empathy-ui-utils.h"
46 #include "empathy-images.h"
47 #include "empathy-live-search.h"
48 #include "empathy-smiley-manager.h"
50 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
51 #include <libempathy/empathy-debug.h>
52 #include <libempathy/empathy-utils.h>
53 #include <libempathy/empathy-ft-factory.h>
56 empathy_gtk_init (void)
58 static gboolean initialized
= FALSE
;
64 gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
65 PKGDATADIR G_DIR_SEPARATOR_S
"icons");
71 builder_get_file_valist (const gchar
*filename
,
72 const gchar
*first_object
,
80 DEBUG ("Loading file %s", filename
);
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
);
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
**);
101 for (name
= first_object
; name
; name
= va_arg (args
, const gchar
*)) {
102 object_ptr
= va_arg (args
, GObject
**);
104 *object_ptr
= gtk_builder_get_object (gui
, name
);
107 g_warning ("File is missing object '%s'.", name
);
116 empathy_builder_get_file (const gchar
*filename
,
117 const gchar
*first_object
,
123 va_start (args
, first_object
);
124 gui
= builder_get_file_valist (filename
, first_object
, args
);
131 empathy_builder_connect (GtkBuilder
*gui
,
133 const gchar
*first_object
,
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
);
147 object
= gtk_builder_get_object (gui
, name
);
149 g_warning ("File is missing object '%s'.", name
);
153 g_signal_connect (object
, sig
, callback
, user_data
);
160 empathy_builder_unref_and_keep_widget (GtkBuilder
*gui
,
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. */
169 g_object_ref (widget
);
170 g_object_force_floating (G_OBJECT (widget
));
171 g_object_unref (gui
);
177 empathy_icon_name_for_presence (TpConnectionPresenceType 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
;
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
;
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
:
216 empathy_icon_name_for_contact (EmpathyContact
*contact
)
218 TpConnectionPresenceType presence
;
220 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
),
221 EMPATHY_IMAGE_OFFLINE
);
223 presence
= empathy_contact_get_presence (contact
);
224 return empathy_icon_name_for_presence (presence
);
228 empathy_icon_name_for_individual (FolksIndividual
*individual
)
230 FolksPresenceType folks_presence
;
231 TpConnectionPresenceType presence
;
234 folks_presence_details_get_presence_type (
235 FOLKS_PRESENCE_DETAILS (individual
));
236 presence
= empathy_folks_presence_type_to_tp (folks_presence
);
238 return empathy_icon_name_for_presence (presence
);
242 empathy_protocol_name_for_contact (EmpathyContact
*contact
)
246 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
), NULL
);
248 account
= empathy_contact_get_account (contact
);
249 if (account
== NULL
) {
253 return tp_account_get_icon_name (account
);
257 empathy_pixbuf_from_data (gchar
*data
,
260 return empathy_pixbuf_from_data_and_mime (data
, data_size
, NULL
);
264 empathy_pixbuf_from_data_and_mime (gchar
*data
,
268 GdkPixbufLoader
*loader
;
269 GdkPixbufFormat
*format
;
270 GdkPixbuf
*pixbuf
= NULL
;
272 GError
*error
= NULL
;
278 loader
= gdk_pixbuf_loader_new ();
279 if (!gdk_pixbuf_loader_write (loader
, (guchar
*) data
, data_size
, &error
)) {
280 DEBUG ("Failed to write to pixbuf loader: %s",
281 error
? error
->message
: "No error given");
284 if (!gdk_pixbuf_loader_close (loader
, &error
)) {
285 DEBUG ("Failed to close pixbuf loader: %s",
286 error
? error
->message
: "No error given");
290 pixbuf
= gdk_pixbuf_loader_get_pixbuf (loader
);
292 g_object_ref (pixbuf
);
294 if (mime_type
!= NULL
) {
295 format
= gdk_pixbuf_loader_get_format (loader
);
296 mime_types
= gdk_pixbuf_format_get_mime_types (format
);
298 *mime_type
= g_strdup (*mime_types
);
299 if (mime_types
[1] != NULL
) {
300 DEBUG ("Loader supports more than one mime "
301 "type! Picking the first one, %s",
304 g_strfreev (mime_types
);
309 g_clear_error (&error
);
310 g_object_unref (loader
);
318 gboolean preserve_aspect_ratio
;
322 pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader
*loader
,
325 struct SizeData
*data
)
327 g_return_if_fail (width
> 0 && height
> 0);
329 if (data
->preserve_aspect_ratio
&& (data
->width
> 0 || data
->height
> 0)) {
330 if (width
< data
->width
&& height
< data
->height
) {
335 if (data
->width
< 0) {
336 width
= width
* (double) data
->height
/ (gdouble
) height
;
337 height
= data
->height
;
338 } else if (data
->height
< 0) {
339 height
= height
* (double) data
->width
/ (double) width
;
341 } else if ((double) height
* (double) data
->width
>
342 (double) width
* (double) data
->height
) {
343 width
= 0.5 + (double) width
* (double) data
->height
/ (double) height
;
344 height
= data
->height
;
346 height
= 0.5 + (double) height
* (double) data
->width
/ (double) width
;
350 if (data
->width
> 0) {
354 if (data
->height
> 0) {
355 height
= data
->height
;
359 gdk_pixbuf_loader_set_size (loader
, width
, height
);
363 empathy_avatar_pixbuf_roundify (GdkPixbuf
*pixbuf
)
365 gint width
, height
, rowstride
;
368 width
= gdk_pixbuf_get_width (pixbuf
);
369 height
= gdk_pixbuf_get_height (pixbuf
);
370 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
371 pixels
= gdk_pixbuf_get_pixels (pixbuf
);
373 if (width
< 6 || height
< 6) {
381 pixels
[rowstride
+ 3] = 0x80;
382 pixels
[rowstride
* 2 + 3] = 0xC0;
385 pixels
[width
* 4 - 1] = 0;
386 pixels
[width
* 4 - 5] = 0x80;
387 pixels
[width
* 4 - 9] = 0xC0;
388 pixels
[rowstride
+ (width
* 4) - 1] = 0x80;
389 pixels
[(2 * rowstride
) + (width
* 4) - 1] = 0xC0;
392 pixels
[(height
- 1) * rowstride
+ 3] = 0;
393 pixels
[(height
- 1) * rowstride
+ 7] = 0x80;
394 pixels
[(height
- 1) * rowstride
+ 11] = 0xC0;
395 pixels
[(height
- 2) * rowstride
+ 3] = 0x80;
396 pixels
[(height
- 3) * rowstride
+ 3] = 0xC0;
399 pixels
[height
* rowstride
- 1] = 0;
400 pixels
[(height
- 1) * rowstride
- 1] = 0x80;
401 pixels
[(height
- 2) * rowstride
- 1] = 0xC0;
402 pixels
[height
* rowstride
- 5] = 0x80;
403 pixels
[height
* rowstride
- 9] = 0xC0;
407 empathy_gdk_pixbuf_is_opaque (GdkPixbuf
*pixbuf
)
409 gint height
, rowstride
, i
;
413 height
= gdk_pixbuf_get_height (pixbuf
);
414 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
415 pixels
= gdk_pixbuf_get_pixels (pixbuf
);
418 for (i
= 3; i
< rowstride
; i
+=4) {
424 for (i
= 1; i
< height
- 1; i
++) {
425 row
= pixels
+ (i
*rowstride
);
426 if (row
[3] < 0xfe || row
[rowstride
-1] < 0xfe) {
431 row
= pixels
+ ((height
-1) * rowstride
);
432 for (i
= 3; i
< rowstride
; i
+=4) {
442 avatar_pixbuf_from_loader (GdkPixbufLoader
*loader
)
446 pixbuf
= gdk_pixbuf_loader_get_pixbuf (loader
);
447 if (!gdk_pixbuf_get_has_alpha (pixbuf
)) {
448 GdkPixbuf
*rounded_pixbuf
;
450 rounded_pixbuf
= gdk_pixbuf_new (GDK_COLORSPACE_RGB
, TRUE
, 8,
451 gdk_pixbuf_get_width (pixbuf
),
452 gdk_pixbuf_get_height (pixbuf
));
453 gdk_pixbuf_copy_area (pixbuf
, 0, 0,
454 gdk_pixbuf_get_width (pixbuf
),
455 gdk_pixbuf_get_height (pixbuf
),
458 pixbuf
= rounded_pixbuf
;
460 g_object_ref (pixbuf
);
463 if (empathy_gdk_pixbuf_is_opaque (pixbuf
)) {
464 empathy_avatar_pixbuf_roundify (pixbuf
);
471 empathy_pixbuf_from_avatar_scaled (EmpathyAvatar
*avatar
,
476 GdkPixbufLoader
*loader
;
477 struct SizeData data
;
478 GError
*error
= NULL
;
485 data
.height
= height
;
486 data
.preserve_aspect_ratio
= TRUE
;
488 loader
= gdk_pixbuf_loader_new ();
490 g_signal_connect (loader
, "size-prepared",
491 G_CALLBACK (pixbuf_from_avatar_size_prepared_cb
),
494 if (!gdk_pixbuf_loader_write (loader
, avatar
->data
, avatar
->len
, &error
)) {
495 g_warning ("Couldn't write avatar image:%p with "
496 "length:%" G_GSIZE_FORMAT
" to pixbuf loader: %s",
497 avatar
->data
, avatar
->len
, error
->message
);
498 g_error_free (error
);
502 gdk_pixbuf_loader_close (loader
, NULL
);
503 pixbuf
= avatar_pixbuf_from_loader (loader
);
505 g_object_unref (loader
);
511 empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact
*contact
,
515 EmpathyAvatar
*avatar
;
517 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
), NULL
);
519 avatar
= empathy_contact_get_avatar (contact
);
521 return empathy_pixbuf_from_avatar_scaled (avatar
, width
, height
);
525 FolksIndividual
*individual
;
526 GSimpleAsyncResult
*result
;
529 } PixbufAvatarFromIndividualClosure
;
531 static PixbufAvatarFromIndividualClosure
*
532 pixbuf_avatar_from_individual_closure_new (FolksIndividual
*individual
,
533 GSimpleAsyncResult
*result
,
537 PixbufAvatarFromIndividualClosure
*closure
;
539 g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual
), NULL
);
540 g_return_val_if_fail (G_IS_ASYNC_RESULT (result
), NULL
);
542 closure
= g_new0 (PixbufAvatarFromIndividualClosure
, 1);
543 closure
->individual
= g_object_ref (individual
);
544 closure
->result
= g_object_ref (result
);
545 closure
->width
= width
;
546 closure
->height
= height
;
552 pixbuf_avatar_from_individual_closure_free (
553 PixbufAvatarFromIndividualClosure
*closure
)
555 g_object_unref (closure
->individual
);
556 g_object_unref (closure
->result
);
561 avatar_file_load_contents_cb (GObject
*object
,
562 GAsyncResult
*result
,
565 GFile
*file
= G_FILE (object
);
566 PixbufAvatarFromIndividualClosure
*closure
= user_data
;
569 struct SizeData size_data
;
570 GError
*error
= NULL
;
571 GdkPixbufLoader
*loader
= NULL
;
573 if (!g_file_load_contents_finish (file
, result
, &data
, &data_size
,
575 DEBUG ("failed to load avatar from file: %s",
577 g_simple_async_result_set_from_error (closure
->result
, error
);
581 size_data
.width
= closure
->width
;
582 size_data
.height
= closure
->height
;
583 size_data
.preserve_aspect_ratio
= TRUE
;
585 loader
= gdk_pixbuf_loader_new ();
587 g_signal_connect (loader
, "size-prepared",
588 G_CALLBACK (pixbuf_from_avatar_size_prepared_cb
),
591 if (!gdk_pixbuf_loader_write (loader
, (guchar
*) data
, data_size
,
593 DEBUG ("Failed to write to pixbuf loader: %s",
594 error
? error
->message
: "No error given");
595 g_simple_async_result_set_from_error (closure
->result
, error
);
598 if (!gdk_pixbuf_loader_close (loader
, &error
)) {
599 DEBUG ("Failed to close pixbuf loader: %s",
600 error
? error
->message
: "No error given");
601 g_simple_async_result_set_from_error (closure
->result
, error
);
605 g_simple_async_result_set_op_res_gpointer (closure
->result
,
606 avatar_pixbuf_from_loader (loader
), g_object_unref
);
609 g_simple_async_result_complete (closure
->result
);
611 g_clear_error (&error
);
613 tp_clear_object (&loader
);
614 pixbuf_avatar_from_individual_closure_free (closure
);
618 empathy_pixbuf_avatar_from_individual_scaled_async (
619 FolksIndividual
*individual
,
622 GCancellable
*cancellable
,
623 GAsyncReadyCallback callback
,
627 GSimpleAsyncResult
*result
;
628 PixbufAvatarFromIndividualClosure
*closure
;
630 result
= g_simple_async_result_new (G_OBJECT (individual
),
632 empathy_pixbuf_avatar_from_individual_scaled_async
);
635 folks_avatar_details_get_avatar (FOLKS_AVATAR_DETAILS (individual
));
636 if (avatar_file
== NULL
)
639 closure
= pixbuf_avatar_from_individual_closure_new (individual
, result
,
644 g_file_load_contents_async (avatar_file
, cancellable
,
645 avatar_file_load_contents_cb
, closure
);
647 g_object_unref (result
);
652 g_simple_async_result_set_op_res_gpointer (result
, NULL
, NULL
);
653 g_simple_async_result_complete (result
);
654 g_object_unref (result
);
657 /* Return a ref on the GdkPixbuf */
659 empathy_pixbuf_avatar_from_individual_scaled_finish (
660 FolksIndividual
*individual
,
661 GAsyncResult
*result
,
664 GSimpleAsyncResult
*simple
= G_SIMPLE_ASYNC_RESULT (result
);
665 gboolean result_valid
;
668 g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual
), NULL
);
669 g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple
), NULL
);
671 if (g_simple_async_result_propagate_error (simple
, error
))
674 result_valid
= g_simple_async_result_is_valid (result
,
675 G_OBJECT (individual
),
676 empathy_pixbuf_avatar_from_individual_scaled_async
);
677 g_return_val_if_fail (result_valid
, NULL
);
679 pixbuf
= g_simple_async_result_get_op_res_gpointer (simple
);
680 return pixbuf
!= NULL
? g_object_ref (pixbuf
) : NULL
;
684 empathy_pixbuf_contact_status_icon (EmpathyContact
*contact
,
685 gboolean show_protocol
)
687 const gchar
*icon_name
;
689 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
), NULL
);
691 icon_name
= empathy_icon_name_for_contact (contact
);
693 if (icon_name
== NULL
) {
696 return empathy_pixbuf_contact_status_icon_with_icon_name (contact
,
702 empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact
*contact
,
703 const gchar
*icon_name
,
704 gboolean show_protocol
)
706 GdkPixbuf
*pix_status
;
707 GdkPixbuf
*pix_protocol
;
708 gchar
*icon_filename
;
710 gint numerator
, denominator
;
712 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
) ||
713 (show_protocol
== FALSE
), NULL
);
714 g_return_val_if_fail (icon_name
!= NULL
, NULL
);
719 icon_filename
= empathy_filename_from_icon_name (icon_name
,
721 if (icon_filename
== NULL
) {
722 DEBUG ("icon name: %s could not be found\n", icon_name
);
726 pix_status
= gdk_pixbuf_new_from_file (icon_filename
, NULL
);
728 if (pix_status
== NULL
) {
729 DEBUG ("Could not open icon %s\n", icon_filename
);
730 g_free (icon_filename
);
734 g_free (icon_filename
);
739 height
= gdk_pixbuf_get_height (pix_status
);
740 width
= gdk_pixbuf_get_width (pix_status
);
742 pix_protocol
= empathy_pixbuf_protocol_from_contact_scaled (contact
,
743 width
* numerator
/ denominator
,
744 height
* numerator
/ denominator
);
746 if (pix_protocol
== NULL
) {
749 gdk_pixbuf_composite (pix_protocol
, pix_status
,
750 0, height
- height
* numerator
/ denominator
,
751 width
* numerator
/ denominator
, height
* numerator
/ denominator
,
752 0, height
- height
* numerator
/ denominator
,
754 GDK_INTERP_BILINEAR
, 255);
756 g_object_unref (pix_protocol
);
762 empathy_pixbuf_protocol_from_contact_scaled (EmpathyContact
*contact
,
768 GdkPixbuf
*pixbuf
= NULL
;
770 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
), NULL
);
772 account
= empathy_contact_get_account (contact
);
773 filename
= empathy_filename_from_icon_name (tp_account_get_icon_name (account
),
775 if (filename
!= NULL
) {
776 pixbuf
= gdk_pixbuf_new_from_file_at_size (filename
, width
, height
, NULL
);
784 empathy_pixbuf_scale_down_if_necessary (GdkPixbuf
*pixbuf
, gint max_size
)
789 width
= gdk_pixbuf_get_width (pixbuf
);
790 height
= gdk_pixbuf_get_height (pixbuf
);
792 if (width
> 0 && (width
> max_size
|| height
> max_size
)) {
793 factor
= (gdouble
) max_size
/ MAX (width
, height
);
795 width
= width
* factor
;
796 height
= height
* factor
;
798 return gdk_pixbuf_scale_simple (pixbuf
,
803 return g_object_ref (pixbuf
);
807 empathy_pixbuf_from_icon_name_sized (const gchar
*icon_name
,
812 GError
*error
= NULL
;
818 theme
= gtk_icon_theme_get_default ();
820 pixbuf
= gtk_icon_theme_load_icon (theme
,
826 DEBUG ("Error loading icon: %s", error
->message
);
827 g_clear_error (&error
);
834 empathy_pixbuf_from_icon_name (const gchar
*icon_name
,
835 GtkIconSize icon_size
)
844 if (gtk_icon_size_lookup (icon_size
, &w
, &h
)) {
848 return empathy_pixbuf_from_icon_name_sized (icon_name
, size
);
852 empathy_filename_from_icon_name (const gchar
*icon_name
,
853 GtkIconSize icon_size
)
855 GtkIconTheme
*icon_theme
;
856 GtkIconInfo
*icon_info
;
861 icon_theme
= gtk_icon_theme_get_default ();
863 if (gtk_icon_size_lookup (icon_size
, &w
, &h
)) {
867 icon_info
= gtk_icon_theme_lookup_icon (icon_theme
, icon_name
, size
, 0);
868 ret
= g_strdup (gtk_icon_info_get_filename (icon_info
));
869 gtk_icon_info_free (icon_info
);
874 /* Stolen from GtkSourceView, hence the weird intendation. Please keep it like
875 * that to make it easier to apply changes from the original code.
877 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
879 /* this function acts like g_utf8_offset_to_pointer() except that if it finds a
880 * decomposable character it consumes the decomposition length from the given
881 * offset. So it's useful when the offset was calculated for the normalized
882 * version of str, but we need a pointer to str itself. */
884 pointer_from_offset_skipping_decomp (const gchar
*str
, gint offset
)
886 gchar
*casefold
, *normal
;
892 q
= g_utf8_next_char (p
);
893 casefold
= g_utf8_casefold (p
, q
- p
);
894 normal
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
895 offset
-= g_utf8_strlen (normal
, -1);
904 g_utf8_strcasestr (const gchar
*haystack
, const gchar
*needle
)
908 const gchar
*ret
= NULL
;
911 gchar
*caseless_haystack
;
914 g_return_val_if_fail (haystack
!= NULL
, NULL
);
915 g_return_val_if_fail (needle
!= NULL
, NULL
);
917 casefold
= g_utf8_casefold (haystack
, -1);
918 caseless_haystack
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
921 needle_len
= g_utf8_strlen (needle
, -1);
922 haystack_len
= g_utf8_strlen (caseless_haystack
, -1);
926 ret
= (gchar
*) haystack
;
930 if (haystack_len
< needle_len
)
936 p
= (gchar
*) caseless_haystack
;
937 needle_len
= strlen (needle
);
942 if ((strncmp (p
, needle
, needle_len
) == 0))
944 ret
= pointer_from_offset_skipping_decomp (haystack
, i
);
948 p
= g_utf8_next_char (p
);
953 g_free (caseless_haystack
);
959 g_utf8_caselessnmatch (const char *s1
, const char *s2
,
960 gssize n1
, gssize n2
)
963 gchar
*normalized_s1
;
964 gchar
*normalized_s2
;
967 gboolean ret
= FALSE
;
969 g_return_val_if_fail (s1
!= NULL
, FALSE
);
970 g_return_val_if_fail (s2
!= NULL
, FALSE
);
971 g_return_val_if_fail (n1
> 0, FALSE
);
972 g_return_val_if_fail (n2
> 0, FALSE
);
974 casefold
= g_utf8_casefold (s1
, n1
);
975 normalized_s1
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
978 casefold
= g_utf8_casefold (s2
, n2
);
979 normalized_s2
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
982 len_s1
= strlen (normalized_s1
);
983 len_s2
= strlen (normalized_s2
);
988 ret
= (strncmp (normalized_s1
, normalized_s2
, len_s2
) == 0);
991 g_free (normalized_s1
);
992 g_free (normalized_s2
);
998 forward_chars_with_skipping (GtkTextIter
*iter
,
1000 gboolean skip_invisible
,
1001 gboolean skip_nontext
,
1002 gboolean skip_decomp
)
1006 g_return_if_fail (count
>= 0);
1012 gboolean ignored
= FALSE
;
1014 /* minimal workaround to avoid the infinite loop of bug #168247.
1015 * It doesn't fix the problemjust the symptom...
1017 if (gtk_text_iter_is_end (iter
))
1020 if (skip_nontext
&& gtk_text_iter_get_char (iter
) == GTK_TEXT_UNKNOWN_CHAR
)
1023 if (!ignored
&& skip_invisible
&&
1024 /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE
)
1027 if (!ignored
&& skip_decomp
)
1029 /* being UTF8 correct sucks; this accounts for extra
1030 offsets coming from canonical decompositions of
1031 UTF8 characters (e.g. accented characters) which
1032 g_utf8_normalize () performs */
1037 buffer_len
= g_unichar_to_utf8 (gtk_text_iter_get_char (iter
), buffer
);
1038 normal
= g_utf8_normalize (buffer
, buffer_len
, G_NORMALIZE_NFD
);
1039 i
-= (g_utf8_strlen (normal
, -1) - 1);
1043 gtk_text_iter_forward_char (iter
);
1051 lines_match (const GtkTextIter
*start
,
1052 const gchar
**lines
,
1053 gboolean visible_only
,
1055 GtkTextIter
*match_start
,
1056 GtkTextIter
*match_end
)
1063 if (*lines
== NULL
|| **lines
== '\0')
1066 *match_start
= *start
;
1068 *match_end
= *start
;
1073 gtk_text_iter_forward_line (&next
);
1075 /* No more text in buffer, but *lines is nonempty */
1076 if (gtk_text_iter_equal (start
, &next
))
1082 line_text
= gtk_text_iter_get_visible_slice (start
, &next
);
1084 line_text
= gtk_text_iter_get_slice (start
, &next
);
1089 line_text
= gtk_text_iter_get_visible_text (start
, &next
);
1091 line_text
= gtk_text_iter_get_text (start
, &next
);
1094 if (match_start
) /* if this is the first line we're matching */
1096 found
= g_utf8_strcasestr (line_text
, *lines
);
1100 /* If it's not the first line, we have to match from the
1101 * start of the line.
1103 if (g_utf8_caselessnmatch (line_text
, *lines
, strlen (line_text
),
1116 /* Get offset to start of search string */
1117 offset
= g_utf8_strlen (line_text
, found
- line_text
);
1121 /* If match start needs to be returned, set it to the
1122 * start of the search string.
1124 forward_chars_with_skipping (&next
, offset
, visible_only
, !slice
, FALSE
);
1127 *match_start
= next
;
1130 /* Go to end of search string */
1131 forward_chars_with_skipping (&next
, g_utf8_strlen (*lines
, -1), visible_only
, !slice
, TRUE
);
1140 /* pass NULL for match_start, since we don't need to find the
1143 return lines_match (&next
, lines
, visible_only
, slice
, NULL
, match_end
);
1146 /* strsplit () that retains the delimiter as part of the string. */
1148 strbreakup (const char *string
,
1149 const char *delimiter
,
1152 GSList
*string_list
= NULL
, *slist
;
1153 gchar
**str_array
, *s
, *casefold
, *new_string
;
1156 g_return_val_if_fail (string
!= NULL
, NULL
);
1157 g_return_val_if_fail (delimiter
!= NULL
, NULL
);
1160 max_tokens
= G_MAXINT
;
1162 s
= strstr (string
, delimiter
);
1165 guint delimiter_len
= strlen (delimiter
);
1171 len
= s
- string
+ delimiter_len
;
1172 new_string
= g_new (gchar
, len
+ 1);
1173 strncpy (new_string
, string
, len
);
1174 new_string
[len
] = 0;
1175 casefold
= g_utf8_casefold (new_string
, -1);
1176 g_free (new_string
);
1177 new_string
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1179 string_list
= g_slist_prepend (string_list
, new_string
);
1181 string
= s
+ delimiter_len
;
1182 s
= strstr (string
, delimiter
);
1183 } while (--max_tokens
&& s
);
1189 casefold
= g_utf8_casefold (string
, -1);
1190 new_string
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1192 string_list
= g_slist_prepend (string_list
, new_string
);
1195 str_array
= g_new (gchar
*, n
);
1199 str_array
[i
--] = NULL
;
1200 for (slist
= string_list
; slist
; slist
= slist
->next
)
1201 str_array
[i
--] = slist
->data
;
1203 g_slist_free (string_list
);
1209 empathy_text_iter_forward_search (const GtkTextIter
*iter
,
1211 GtkTextIter
*match_start
,
1212 GtkTextIter
*match_end
,
1213 const GtkTextIter
*limit
)
1215 gchar
**lines
= NULL
;
1217 gboolean retval
= FALSE
;
1219 gboolean visible_only
;
1222 g_return_val_if_fail (iter
!= NULL
, FALSE
);
1223 g_return_val_if_fail (str
!= NULL
, FALSE
);
1225 if (limit
&& gtk_text_iter_compare (iter
, limit
) >= 0)
1229 /* If we can move one char, return the empty string there */
1232 if (gtk_text_iter_forward_char (&match
)) {
1233 if (limit
&& gtk_text_iter_equal (&match
, limit
)) {
1238 *match_start
= match
;
1249 visible_only
= TRUE
;
1252 /* locate all lines */
1253 lines
= strbreakup (str
, "\n", -1);
1258 /* This loop has an inefficient worst-case, where
1259 * gtk_text_iter_get_text () is called repeatedly on
1264 if (limit
&& gtk_text_iter_compare (&search
, limit
) >= 0) {
1268 if (lines_match (&search
, (const gchar
**)lines
,
1269 visible_only
, slice
, &match
, &end
)) {
1270 if (limit
== NULL
||
1271 (limit
&& gtk_text_iter_compare (&end
, limit
) <= 0)) {
1275 *match_start
= match
;
1283 } while (gtk_text_iter_forward_line (&search
));
1285 g_strfreev ((gchar
**) lines
);
1290 static const gchar
*
1291 g_utf8_strrcasestr (const gchar
*haystack
, const gchar
*needle
)
1295 const gchar
*ret
= NULL
;
1298 gchar
*caseless_haystack
;
1301 g_return_val_if_fail (haystack
!= NULL
, NULL
);
1302 g_return_val_if_fail (needle
!= NULL
, NULL
);
1304 casefold
= g_utf8_casefold (haystack
, -1);
1305 caseless_haystack
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1308 needle_len
= g_utf8_strlen (needle
, -1);
1309 haystack_len
= g_utf8_strlen (caseless_haystack
, -1);
1311 if (needle_len
== 0)
1313 ret
= (gchar
*) haystack
;
1317 if (haystack_len
< needle_len
)
1323 i
= haystack_len
- needle_len
;
1324 p
= g_utf8_offset_to_pointer (caseless_haystack
, i
);
1325 needle_len
= strlen (needle
);
1327 while (p
>= caseless_haystack
)
1329 if (strncmp (p
, needle
, needle_len
) == 0)
1331 ret
= pointer_from_offset_skipping_decomp (haystack
, i
);
1335 p
= g_utf8_prev_char (p
);
1340 g_free (caseless_haystack
);
1346 backward_lines_match (const GtkTextIter
*start
,
1347 const gchar
**lines
,
1348 gboolean visible_only
,
1350 GtkTextIter
*match_start
,
1351 GtkTextIter
*match_end
)
1353 GtkTextIter line
, next
;
1358 if (*lines
== NULL
|| **lines
== '\0')
1361 *match_start
= *start
;
1363 *match_end
= *start
;
1367 line
= next
= *start
;
1368 if (gtk_text_iter_get_line_offset (&next
) == 0)
1370 if (!gtk_text_iter_backward_line (&next
))
1374 gtk_text_iter_set_line_offset (&next
, 0);
1379 line_text
= gtk_text_iter_get_visible_slice (&next
, &line
);
1381 line_text
= gtk_text_iter_get_slice (&next
, &line
);
1386 line_text
= gtk_text_iter_get_visible_text (&next
, &line
);
1388 line_text
= gtk_text_iter_get_text (&next
, &line
);
1391 if (match_start
) /* if this is the first line we're matching */
1393 found
= g_utf8_strrcasestr (line_text
, *lines
);
1397 /* If it's not the first line, we have to match from the
1398 * start of the line.
1400 if (g_utf8_caselessnmatch (line_text
, *lines
, strlen (line_text
),
1413 /* Get offset to start of search string */
1414 offset
= g_utf8_strlen (line_text
, found
- line_text
);
1416 forward_chars_with_skipping (&next
, offset
, visible_only
, !slice
, FALSE
);
1418 /* If match start needs to be returned, set it to the
1419 * start of the search string.
1423 *match_start
= next
;
1426 /* Go to end of search string */
1427 forward_chars_with_skipping (&next
, g_utf8_strlen (*lines
, -1), visible_only
, !slice
, TRUE
);
1436 /* try to match the rest of the lines forward, passing NULL
1437 * for match_start so lines_match will try to match the entire
1439 return lines_match (&next
, lines
, visible_only
,
1440 slice
, NULL
, match_end
);
1444 empathy_text_iter_backward_search (const GtkTextIter
*iter
,
1446 GtkTextIter
*match_start
,
1447 GtkTextIter
*match_end
,
1448 const GtkTextIter
*limit
)
1450 gchar
**lines
= NULL
;
1452 gboolean retval
= FALSE
;
1454 gboolean visible_only
;
1457 g_return_val_if_fail (iter
!= NULL
, FALSE
);
1458 g_return_val_if_fail (str
!= NULL
, FALSE
);
1460 if (limit
&& gtk_text_iter_compare (iter
, limit
) <= 0)
1465 /* If we can move one char, return the empty string there */
1468 if (gtk_text_iter_backward_char (&match
))
1470 if (limit
&& gtk_text_iter_equal (&match
, limit
))
1474 *match_start
= match
;
1485 visible_only
= TRUE
;
1488 /* locate all lines */
1489 lines
= strbreakup (str
, "\n", -1);
1495 /* This loop has an inefficient worst-case, where
1496 * gtk_text_iter_get_text () is called repeatedly on
1501 if (limit
&& gtk_text_iter_compare (&search
, limit
) <= 0)
1504 if (backward_lines_match (&search
, (const gchar
**)lines
,
1505 visible_only
, slice
, &match
, &end
))
1507 if (limit
== NULL
|| (limit
&&
1508 gtk_text_iter_compare (&end
, limit
) > 0))
1513 *match_start
= match
;
1520 if (gtk_text_iter_get_line_offset (&search
) == 0)
1522 if (!gtk_text_iter_backward_line (&search
))
1527 gtk_text_iter_set_line_offset (&search
, 0);
1531 g_strfreev ((gchar
**) lines
);
1536 /* Takes care of moving the window to the current workspace. */
1538 empathy_window_present_with_time (GtkWindow
*window
,
1541 GdkWindow
*gdk_window
;
1543 g_return_if_fail (GTK_IS_WINDOW (window
));
1545 /* Move the window to the current workspace before trying to show it.
1546 * This is the behaviour people expect when clicking on the statusbar icon. */
1547 gdk_window
= gtk_widget_get_window (GTK_WIDGET (window
));
1552 /* Has no effect if the WM has viewports, like compiz */
1553 gdk_x11_window_move_to_current_desktop (gdk_window
);
1555 /* If window is still off-screen, hide it to force it to
1556 * reposition on the current workspace. */
1557 gtk_window_get_position (window
, &x
, &y
);
1558 gtk_window_get_size (window
, &w
, &h
);
1559 if (!EMPATHY_RECT_IS_ON_SCREEN (x
, y
, w
, h
))
1560 gtk_widget_hide (GTK_WIDGET (window
));
1563 if (timestamp
== GDK_CURRENT_TIME
)
1564 gtk_window_present (window
);
1566 gtk_window_present_with_time (window
, timestamp
);
1570 empathy_window_present (GtkWindow
*window
)
1572 empathy_window_present_with_time (window
, gtk_get_current_event_time ());
1576 empathy_get_toplevel_window (GtkWidget
*widget
)
1578 GtkWidget
*toplevel
;
1580 g_return_val_if_fail (GTK_IS_WIDGET (widget
), NULL
);
1582 toplevel
= gtk_widget_get_toplevel (widget
);
1583 if (GTK_IS_WINDOW (toplevel
) &&
1584 gtk_widget_is_toplevel (toplevel
)) {
1585 return GTK_WINDOW (toplevel
);
1591 /** empathy_make_absolute_url_len:
1595 * Same as #empathy_make_absolute_url but for a limited string length
1598 empathy_make_absolute_url_len (const gchar
*url
,
1601 g_return_val_if_fail (url
!= NULL
, NULL
);
1603 if (g_str_has_prefix (url
, "ghelp:") ||
1604 g_str_has_prefix (url
, "mailto:") ||
1605 strstr (url
, ":/")) {
1606 return g_strndup (url
, len
);
1609 if (strstr (url
, "@")) {
1610 return g_strdup_printf ("mailto:%.*s", len
, url
);
1613 return g_strdup_printf ("http://%.*s", len
, url
);
1616 /** empathy_make_absolute_url:
1619 * The URL opening code can't handle schemeless strings, so we try to be
1620 * smart and add http if there is no scheme or doesn't look like a mail
1621 * address. This should work in most cases, and let us click on strings
1622 * like "www.gnome.org".
1624 * Returns: a newly allocated url with proper mailto: or http:// prefix, use
1625 * g_free when your are done with it
1628 empathy_make_absolute_url (const gchar
*url
)
1630 return empathy_make_absolute_url_len (url
, strlen (url
));
1634 empathy_url_show (GtkWidget
*parent
,
1638 GError
*error
= NULL
;
1640 g_return_if_fail (parent
== NULL
|| GTK_IS_WIDGET (parent
));
1641 g_return_if_fail (url
!= NULL
);
1643 real_url
= empathy_make_absolute_url (url
);
1645 gtk_show_uri (parent
? gtk_widget_get_screen (parent
) : NULL
, real_url
,
1646 gtk_get_current_event_time (), &error
);
1651 dialog
= gtk_message_dialog_new (NULL
, 0,
1652 GTK_MESSAGE_ERROR
, GTK_BUTTONS_CLOSE
,
1653 _("Unable to open URI"));
1654 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog
),
1655 "%s", error
->message
);
1657 g_signal_connect (dialog
, "response",
1658 G_CALLBACK (gtk_widget_destroy
),
1660 gtk_window_present (GTK_WINDOW (dialog
));
1662 g_clear_error (&error
);
1669 empathy_send_file (EmpathyContact
*contact
, GFile
*file
)
1671 EmpathyFTFactory
*factory
;
1672 GtkRecentManager
*manager
;
1675 g_return_if_fail (EMPATHY_IS_CONTACT (contact
));
1676 g_return_if_fail (G_IS_FILE (file
));
1678 factory
= empathy_ft_factory_dup_singleton ();
1680 empathy_ft_factory_new_transfer_outgoing (factory
, contact
, file
);
1682 uri
= g_file_get_uri (file
);
1683 manager
= gtk_recent_manager_get_default ();
1684 gtk_recent_manager_add_item (manager
, uri
);
1687 g_object_unref (factory
);
1691 empathy_send_file_from_uri_list (EmpathyContact
*contact
, const gchar
*uri_list
)
1696 /* Only handle a single file for now. It would be wicked cool to be
1697 able to do multiple files, offering to zip them or whatever like
1698 nautilus-sendto does. Note that text/uri-list is defined to have
1699 each line terminated by \r\n, but we can be tolerant of applications
1700 that only use \n or don't terminate single-line entries.
1702 nl
= strstr (uri_list
, "\r\n");
1704 nl
= strchr (uri_list
, '\n');
1707 gchar
*uri
= g_strndup (uri_list
, nl
- uri_list
);
1708 file
= g_file_new_for_uri (uri
);
1712 file
= g_file_new_for_uri (uri_list
);
1715 empathy_send_file (contact
, file
);
1717 g_object_unref (file
);
1721 file_manager_send_file_response_cb (GtkDialog
*widget
,
1723 EmpathyContact
*contact
)
1727 if (response_id
== GTK_RESPONSE_OK
) {
1728 file
= gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget
));
1730 empathy_send_file (contact
, file
);
1732 g_object_unref (file
);
1735 gtk_widget_destroy (GTK_WIDGET (widget
));
1739 empathy_send_file_with_file_chooser (EmpathyContact
*contact
)
1744 g_return_if_fail (EMPATHY_IS_CONTACT (contact
));
1746 DEBUG ("Creating selection file chooser");
1748 widget
= gtk_file_chooser_dialog_new (_("Select a file"),
1750 GTK_FILE_CHOOSER_ACTION_OPEN
,
1752 GTK_RESPONSE_CANCEL
,
1756 button
= gtk_button_new_with_mnemonic (_("_Send"));
1757 gtk_button_set_image (GTK_BUTTON (button
),
1758 gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND
,
1759 GTK_ICON_SIZE_BUTTON
));
1760 gtk_widget_show (button
);
1761 gtk_dialog_add_action_widget (GTK_DIALOG (widget
), button
,
1763 gtk_widget_set_can_default (button
, TRUE
);
1764 gtk_dialog_set_default_response (GTK_DIALOG (widget
),
1767 gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget
), FALSE
);
1769 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget
),
1772 g_signal_connect (widget
, "response",
1773 G_CALLBACK (file_manager_send_file_response_cb
),
1776 gtk_widget_show (widget
);
1780 file_manager_receive_file_response_cb (GtkDialog
*dialog
,
1781 GtkResponseType response
,
1782 EmpathyFTHandler
*handler
)
1784 EmpathyFTFactory
*factory
;
1787 if (response
== GTK_RESPONSE_OK
) {
1790 guint64 free_space
, file_size
;
1791 GError
*error
= NULL
;
1793 file
= gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog
));
1794 parent
= g_file_get_parent (file
);
1795 info
= g_file_query_filesystem_info (parent
,
1796 G_FILE_ATTRIBUTE_FILESYSTEM_FREE
,
1799 g_object_unref (parent
);
1801 if (error
!= NULL
) {
1802 g_warning ("Error: %s", error
->message
);
1804 g_object_unref (file
);
1808 free_space
= g_file_info_get_attribute_uint64 (info
,
1809 G_FILE_ATTRIBUTE_FILESYSTEM_FREE
);
1810 file_size
= empathy_ft_handler_get_total_bytes (handler
);
1812 g_object_unref (info
);
1814 if (file_size
> free_space
) {
1815 GtkWidget
*message
= gtk_message_dialog_new (
1816 GTK_WINDOW (dialog
),
1820 _("Insufficient free space to save file"));
1821 char *file_size_str
, *free_space_str
;
1823 file_size_str
= g_format_size_for_display (file_size
);
1824 free_space_str
= g_format_size_for_display (free_space
);
1826 gtk_message_dialog_format_secondary_text (
1827 GTK_MESSAGE_DIALOG (message
),
1828 _("%s of free space are required to save this "
1829 "file, but only %s is available. Please "
1830 "choose another location."),
1831 file_size_str
, free_space_str
);
1833 gtk_dialog_run (GTK_DIALOG (message
));
1835 g_free (file_size_str
);
1836 g_free (free_space_str
);
1837 gtk_widget_destroy (message
);
1839 g_object_unref (file
);
1844 factory
= empathy_ft_factory_dup_singleton ();
1846 empathy_ft_factory_set_destination_for_incoming_handler (
1847 factory
, handler
, file
);
1849 g_object_unref (factory
);
1850 g_object_unref (file
);
1852 /* unref the handler, as we dismissed the file chooser,
1853 * and refused the transfer.
1855 g_object_unref (handler
);
1858 gtk_widget_destroy (GTK_WIDGET (dialog
));
1862 empathy_receive_file_with_file_chooser (EmpathyFTHandler
*handler
)
1866 EmpathyContact
*contact
;
1869 contact
= empathy_ft_handler_get_contact (handler
);
1870 g_assert (contact
!= NULL
);
1872 title
= g_strdup_printf (_("Incoming file from %s"),
1873 empathy_contact_get_alias (contact
));
1875 widget
= gtk_file_chooser_dialog_new (title
,
1877 GTK_FILE_CHOOSER_ACTION_SAVE
,
1879 GTK_RESPONSE_CANCEL
,
1883 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget
),
1884 empathy_ft_handler_get_filename (handler
));
1885 gtk_file_chooser_set_do_overwrite_confirmation
1886 (GTK_FILE_CHOOSER (widget
), TRUE
);
1888 dir
= g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD
);
1890 /* Fallback to $HOME if $XDG_DOWNLOAD_DIR is not set */
1891 dir
= g_get_home_dir ();
1893 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget
), dir
);
1895 g_signal_connect (widget
, "response",
1896 G_CALLBACK (file_manager_receive_file_response_cb
), handler
);
1898 gtk_widget_show (widget
);
1903 empathy_make_color_whiter (GdkRGBA
*color
)
1905 const GdkRGBA white
= { 1.0, 1.0, 1.0, 1.0 };
1907 color
->red
= (color
->red
+ white
.red
) / 2;
1908 color
->green
= (color
->green
+ white
.green
) / 2;
1909 color
->blue
= (color
->blue
+ white
.blue
) / 2;
1913 menu_deactivate_cb (GtkMenu
*menu
,
1916 /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
1917 g_signal_handlers_disconnect_by_func (menu
,
1918 menu_deactivate_cb
, user_data
);
1920 gtk_menu_detach (menu
);
1923 /* Convenient function to create a GtkMenu attached to @attach_to and detach
1924 * it when the menu is not displayed any more. This is useful when creating a
1925 * context menu that we want to get rid as soon as it as been displayed. */
1927 empathy_context_menu_new (GtkWidget
*attach_to
)
1931 menu
= gtk_menu_new ();
1933 gtk_menu_attach_to_widget (GTK_MENU (menu
), attach_to
, NULL
);
1935 /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
1936 * floating ref. We can either wait that @attach_to releases its ref when
1937 * it will be destroyed (when leaving Empathy most of the time) or explicitely
1938 * detach the menu when it's not displayed any more.
1939 * We go for the latter as we don't want to keep useless menus in memory
1940 * during the whole lifetime of Empathy. */
1941 g_signal_connect (menu
, "deactivate", G_CALLBACK (menu_deactivate_cb
), NULL
);
1947 empathy_get_current_action_time (void)
1949 return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
1952 /* @words = empathy_live_search_strip_utf8_string (@text);
1954 * User has to pass both so we don't have to compute @words ourself each time
1955 * this function is called. */
1957 empathy_individual_match_string (FolksIndividual
*individual
,
1964 gboolean retval
= FALSE
;
1966 /* check alias name */
1967 str
= folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual
));
1969 if (empathy_live_search_match_words (str
, words
))
1972 personas
= folks_individual_get_personas (individual
);
1974 /* check contact id, remove the @server.com part */
1975 iter
= gee_iterable_iterator (GEE_ITERABLE (personas
));
1976 while (retval
== FALSE
&& gee_iterator_next (iter
))
1978 FolksPersona
*persona
= gee_iterator_get (iter
);
1981 if (empathy_folks_persona_is_interesting (persona
))
1983 str
= folks_persona_get_display_id (persona
);
1985 /* Accept the persona if @text is a full prefix of his ID; that allows
1986 * user to find, say, a jabber contact by typing his JID. */
1987 if (!g_str_has_prefix (str
, text
))
1993 gchar
*dup_str
= NULL
;
1996 p
= strstr (str
, "@");
1998 str
= dup_str
= g_strndup (str
, p
- str
);
2000 visible
= empathy_live_search_match_words (str
, words
);
2006 g_clear_object (&persona
);
2008 g_clear_object (&iter
);
2010 /* FIXME: Add more rules here, we could check phone numbers in
2011 * contact's vCard for example. */