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 (avatar
->len
== 0) {
495 g_warning ("Avatar has 0 length");
497 } else if (!gdk_pixbuf_loader_write (loader
, avatar
->data
, avatar
->len
, &error
)) {
498 g_warning ("Couldn't write avatar image:%p with "
499 "length:%" G_GSIZE_FORMAT
" to pixbuf loader: %s",
500 avatar
->data
, avatar
->len
, error
->message
);
501 g_error_free (error
);
505 gdk_pixbuf_loader_close (loader
, NULL
);
506 pixbuf
= avatar_pixbuf_from_loader (loader
);
508 g_object_unref (loader
);
514 empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact
*contact
,
518 EmpathyAvatar
*avatar
;
520 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
), NULL
);
522 avatar
= empathy_contact_get_avatar (contact
);
524 return empathy_pixbuf_from_avatar_scaled (avatar
, width
, height
);
528 FolksIndividual
*individual
;
529 GSimpleAsyncResult
*result
;
532 struct SizeData size_data
;
533 GdkPixbufLoader
*loader
;
534 GCancellable
*cancellable
;
536 } PixbufAvatarFromIndividualClosure
;
538 static PixbufAvatarFromIndividualClosure
*
539 pixbuf_avatar_from_individual_closure_new (FolksIndividual
*individual
,
540 GSimpleAsyncResult
*result
,
543 GCancellable
*cancellable
)
545 PixbufAvatarFromIndividualClosure
*closure
;
547 g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual
), NULL
);
548 g_return_val_if_fail (G_IS_ASYNC_RESULT (result
), NULL
);
550 closure
= g_new0 (PixbufAvatarFromIndividualClosure
, 1);
551 closure
->individual
= g_object_ref (individual
);
552 closure
->result
= g_object_ref (result
);
553 closure
->width
= width
;
554 closure
->height
= height
;
555 if (cancellable
!= NULL
)
556 closure
->cancellable
= g_object_ref (cancellable
);
562 pixbuf_avatar_from_individual_closure_free (
563 PixbufAvatarFromIndividualClosure
*closure
)
565 g_clear_object (&closure
->cancellable
);
566 tp_clear_object (&closure
->loader
);
567 g_object_unref (closure
->individual
);
568 g_object_unref (closure
->result
);
573 avatar_icon_load_close_cb (GObject
*object
,
574 GAsyncResult
*result
,
577 GError
*error
= NULL
;
579 g_input_stream_close_finish (G_INPUT_STREAM (object
), result
, &error
);
582 DEBUG ("Failed to close pixbuf stream: %s", error
->message
);
583 g_error_free (error
);
588 avatar_icon_load_read_cb (GObject
*object
,
589 GAsyncResult
*result
,
592 GInputStream
*stream
= G_INPUT_STREAM (object
);
593 PixbufAvatarFromIndividualClosure
*closure
= user_data
;
595 GError
*error
= NULL
;
597 /* Finish reading this chunk from the stream */
598 n_read
= g_input_stream_read_finish (stream
, result
, &error
);
600 DEBUG ("Failed to finish read from pixbuf stream: %s",
602 g_simple_async_result_set_from_error (closure
->result
, error
);
606 /* Write the chunk to the pixbuf loader */
607 if (!gdk_pixbuf_loader_write (closure
->loader
, (guchar
*) closure
->data
,
609 DEBUG ("Failed to write to pixbuf loader: %s",
610 error
? error
->message
: "No error given");
611 g_simple_async_result_set_from_error (closure
->result
, error
);
617 if (!gdk_pixbuf_loader_close (closure
->loader
, &error
)) {
618 DEBUG ("Failed to close pixbuf loader: %s",
619 error
? error
->message
: "No error given");
620 g_simple_async_result_set_from_error (closure
->result
, error
);
625 g_simple_async_result_set_op_res_gpointer (closure
->result
,
626 avatar_pixbuf_from_loader (closure
->loader
),
631 /* Loop round and read another chunk. */
632 g_input_stream_read_async (stream
, closure
->data
,
633 G_N_ELEMENTS (closure
->data
),
634 G_PRIORITY_DEFAULT
, closure
->cancellable
,
635 avatar_icon_load_read_cb
, closure
);
641 /* We must close the pixbuf loader before unreffing it. */
642 gdk_pixbuf_loader_close (closure
->loader
, NULL
);
645 /* Close the file for safety (even though it should be
646 * automatically closed when the stream is finalised). */
647 g_input_stream_close_async (stream
, G_PRIORITY_DEFAULT
, NULL
,
648 (GAsyncReadyCallback
) avatar_icon_load_close_cb
, NULL
);
650 g_simple_async_result_complete (closure
->result
);
652 g_clear_error (&error
);
653 pixbuf_avatar_from_individual_closure_free (closure
);
657 avatar_icon_load_cb (GObject
*object
,
658 GAsyncResult
*result
,
661 GLoadableIcon
*icon
= G_LOADABLE_ICON (object
);
662 PixbufAvatarFromIndividualClosure
*closure
= user_data
;
663 GInputStream
*stream
;
664 GError
*error
= NULL
;
666 stream
= g_loadable_icon_load_finish (icon
, result
, NULL
, &error
);
668 DEBUG ("Failed to open avatar stream: %s", error
->message
);
669 g_simple_async_result_set_from_error (closure
->result
, error
);
673 closure
->size_data
.width
= closure
->width
;
674 closure
->size_data
.height
= closure
->height
;
675 closure
->size_data
.preserve_aspect_ratio
= TRUE
;
677 /* Load the data into a pixbuf loader in chunks. */
678 closure
->loader
= gdk_pixbuf_loader_new ();
680 g_signal_connect (closure
->loader
, "size-prepared",
681 G_CALLBACK (pixbuf_from_avatar_size_prepared_cb
),
682 &(closure
->size_data
));
684 /* Begin to read the first chunk. */
685 g_input_stream_read_async (stream
, closure
->data
,
686 G_N_ELEMENTS (closure
->data
),
687 G_PRIORITY_DEFAULT
, closure
->cancellable
,
688 avatar_icon_load_read_cb
, closure
);
690 g_object_unref (stream
);
695 g_simple_async_result_complete (closure
->result
);
697 g_clear_error (&error
);
698 tp_clear_object (&stream
);
699 pixbuf_avatar_from_individual_closure_free (closure
);
703 empathy_pixbuf_avatar_from_individual_scaled_async (
704 FolksIndividual
*individual
,
707 GCancellable
*cancellable
,
708 GAsyncReadyCallback callback
,
711 GLoadableIcon
*avatar_icon
;
712 GSimpleAsyncResult
*result
;
713 PixbufAvatarFromIndividualClosure
*closure
;
715 result
= g_simple_async_result_new (G_OBJECT (individual
),
717 empathy_pixbuf_avatar_from_individual_scaled_async
);
720 folks_avatar_details_get_avatar (FOLKS_AVATAR_DETAILS (individual
));
721 if (avatar_icon
== NULL
)
724 closure
= pixbuf_avatar_from_individual_closure_new (individual
, result
,
730 g_loadable_icon_load_async (avatar_icon
, width
, cancellable
,
731 avatar_icon_load_cb
, closure
);
733 g_object_unref (result
);
738 g_simple_async_result_set_op_res_gpointer (result
, NULL
, NULL
);
739 g_simple_async_result_complete (result
);
740 g_object_unref (result
);
743 /* Return a ref on the GdkPixbuf */
745 empathy_pixbuf_avatar_from_individual_scaled_finish (
746 FolksIndividual
*individual
,
747 GAsyncResult
*result
,
750 GSimpleAsyncResult
*simple
= G_SIMPLE_ASYNC_RESULT (result
);
751 gboolean result_valid
;
754 g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual
), NULL
);
755 g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple
), NULL
);
757 if (g_simple_async_result_propagate_error (simple
, error
))
760 result_valid
= g_simple_async_result_is_valid (result
,
761 G_OBJECT (individual
),
762 empathy_pixbuf_avatar_from_individual_scaled_async
);
763 g_return_val_if_fail (result_valid
, NULL
);
765 pixbuf
= g_simple_async_result_get_op_res_gpointer (simple
);
766 return pixbuf
!= NULL
? g_object_ref (pixbuf
) : NULL
;
770 empathy_pixbuf_contact_status_icon (EmpathyContact
*contact
,
771 gboolean show_protocol
)
773 const gchar
*icon_name
;
775 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
), NULL
);
777 icon_name
= empathy_icon_name_for_contact (contact
);
779 if (icon_name
== NULL
) {
782 return empathy_pixbuf_contact_status_icon_with_icon_name (contact
,
788 empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact
*contact
,
789 const gchar
*icon_name
,
790 gboolean show_protocol
)
792 GdkPixbuf
*pix_status
;
793 GdkPixbuf
*pix_protocol
;
794 gchar
*icon_filename
;
796 gint numerator
, denominator
;
798 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
) ||
799 (show_protocol
== FALSE
), NULL
);
800 g_return_val_if_fail (icon_name
!= NULL
, NULL
);
805 icon_filename
= empathy_filename_from_icon_name (icon_name
,
807 if (icon_filename
== NULL
) {
808 DEBUG ("icon name: %s could not be found\n", icon_name
);
812 pix_status
= gdk_pixbuf_new_from_file (icon_filename
, NULL
);
814 if (pix_status
== NULL
) {
815 DEBUG ("Could not open icon %s\n", icon_filename
);
816 g_free (icon_filename
);
820 g_free (icon_filename
);
825 height
= gdk_pixbuf_get_height (pix_status
);
826 width
= gdk_pixbuf_get_width (pix_status
);
828 pix_protocol
= empathy_pixbuf_protocol_from_contact_scaled (contact
,
829 width
* numerator
/ denominator
,
830 height
* numerator
/ denominator
);
832 if (pix_protocol
== NULL
) {
835 gdk_pixbuf_composite (pix_protocol
, pix_status
,
836 0, height
- height
* numerator
/ denominator
,
837 width
* numerator
/ denominator
, height
* numerator
/ denominator
,
838 0, height
- height
* numerator
/ denominator
,
840 GDK_INTERP_BILINEAR
, 255);
842 g_object_unref (pix_protocol
);
848 empathy_pixbuf_protocol_from_contact_scaled (EmpathyContact
*contact
,
854 GdkPixbuf
*pixbuf
= NULL
;
856 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
), NULL
);
858 account
= empathy_contact_get_account (contact
);
859 filename
= empathy_filename_from_icon_name (tp_account_get_icon_name (account
),
861 if (filename
!= NULL
) {
862 pixbuf
= gdk_pixbuf_new_from_file_at_size (filename
, width
, height
, NULL
);
870 empathy_pixbuf_scale_down_if_necessary (GdkPixbuf
*pixbuf
, gint max_size
)
875 width
= gdk_pixbuf_get_width (pixbuf
);
876 height
= gdk_pixbuf_get_height (pixbuf
);
878 if (width
> 0 && (width
> max_size
|| height
> max_size
)) {
879 factor
= (gdouble
) max_size
/ MAX (width
, height
);
881 width
= width
* factor
;
882 height
= height
* factor
;
884 return gdk_pixbuf_scale_simple (pixbuf
,
889 return g_object_ref (pixbuf
);
893 empathy_pixbuf_from_icon_name_sized (const gchar
*icon_name
,
898 GError
*error
= NULL
;
904 theme
= gtk_icon_theme_get_default ();
906 pixbuf
= gtk_icon_theme_load_icon (theme
,
912 DEBUG ("Error loading icon: %s", error
->message
);
913 g_clear_error (&error
);
920 empathy_pixbuf_from_icon_name (const gchar
*icon_name
,
921 GtkIconSize icon_size
)
930 if (gtk_icon_size_lookup (icon_size
, &w
, &h
)) {
934 return empathy_pixbuf_from_icon_name_sized (icon_name
, size
);
938 empathy_filename_from_icon_name (const gchar
*icon_name
,
939 GtkIconSize icon_size
)
941 GtkIconTheme
*icon_theme
;
942 GtkIconInfo
*icon_info
;
947 icon_theme
= gtk_icon_theme_get_default ();
949 if (gtk_icon_size_lookup (icon_size
, &w
, &h
)) {
953 icon_info
= gtk_icon_theme_lookup_icon (icon_theme
, icon_name
, size
, 0);
954 if (icon_info
== NULL
)
957 ret
= g_strdup (gtk_icon_info_get_filename (icon_info
));
958 gtk_icon_info_free (icon_info
);
963 /* Stolen from GtkSourceView, hence the weird intendation. Please keep it like
964 * that to make it easier to apply changes from the original code.
966 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
968 /* this function acts like g_utf8_offset_to_pointer() except that if it finds a
969 * decomposable character it consumes the decomposition length from the given
970 * offset. So it's useful when the offset was calculated for the normalized
971 * version of str, but we need a pointer to str itself. */
973 pointer_from_offset_skipping_decomp (const gchar
*str
, gint offset
)
975 gchar
*casefold
, *normal
;
981 q
= g_utf8_next_char (p
);
982 casefold
= g_utf8_casefold (p
, q
- p
);
983 normal
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
984 offset
-= g_utf8_strlen (normal
, -1);
993 g_utf8_strcasestr (const gchar
*haystack
, const gchar
*needle
)
997 const gchar
*ret
= NULL
;
1000 gchar
*caseless_haystack
;
1003 g_return_val_if_fail (haystack
!= NULL
, NULL
);
1004 g_return_val_if_fail (needle
!= NULL
, NULL
);
1006 casefold
= g_utf8_casefold (haystack
, -1);
1007 caseless_haystack
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1010 needle_len
= g_utf8_strlen (needle
, -1);
1011 haystack_len
= g_utf8_strlen (caseless_haystack
, -1);
1013 if (needle_len
== 0)
1015 ret
= (gchar
*) haystack
;
1019 if (haystack_len
< needle_len
)
1025 p
= (gchar
*) caseless_haystack
;
1026 needle_len
= strlen (needle
);
1031 if ((strncmp (p
, needle
, needle_len
) == 0))
1033 ret
= pointer_from_offset_skipping_decomp (haystack
, i
);
1037 p
= g_utf8_next_char (p
);
1042 g_free (caseless_haystack
);
1048 g_utf8_caselessnmatch (const char *s1
, const char *s2
,
1049 gssize n1
, gssize n2
)
1052 gchar
*normalized_s1
;
1053 gchar
*normalized_s2
;
1056 gboolean ret
= FALSE
;
1058 g_return_val_if_fail (s1
!= NULL
, FALSE
);
1059 g_return_val_if_fail (s2
!= NULL
, FALSE
);
1060 g_return_val_if_fail (n1
> 0, FALSE
);
1061 g_return_val_if_fail (n2
> 0, FALSE
);
1063 casefold
= g_utf8_casefold (s1
, n1
);
1064 normalized_s1
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1067 casefold
= g_utf8_casefold (s2
, n2
);
1068 normalized_s2
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1071 len_s1
= strlen (normalized_s1
);
1072 len_s2
= strlen (normalized_s2
);
1074 if (len_s1
< len_s2
)
1077 ret
= (strncmp (normalized_s1
, normalized_s2
, len_s2
) == 0);
1080 g_free (normalized_s1
);
1081 g_free (normalized_s2
);
1087 forward_chars_with_skipping (GtkTextIter
*iter
,
1089 gboolean skip_invisible
,
1090 gboolean skip_nontext
,
1091 gboolean skip_decomp
)
1095 g_return_if_fail (count
>= 0);
1101 gboolean ignored
= FALSE
;
1103 /* minimal workaround to avoid the infinite loop of bug #168247.
1104 * It doesn't fix the problemjust the symptom...
1106 if (gtk_text_iter_is_end (iter
))
1109 if (skip_nontext
&& gtk_text_iter_get_char (iter
) == GTK_TEXT_UNKNOWN_CHAR
)
1112 if (!ignored
&& skip_invisible
&&
1113 /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE
)
1116 if (!ignored
&& skip_decomp
)
1118 /* being UTF8 correct sucks; this accounts for extra
1119 offsets coming from canonical decompositions of
1120 UTF8 characters (e.g. accented characters) which
1121 g_utf8_normalize () performs */
1126 buffer_len
= g_unichar_to_utf8 (gtk_text_iter_get_char (iter
), buffer
);
1127 normal
= g_utf8_normalize (buffer
, buffer_len
, G_NORMALIZE_NFD
);
1128 i
-= (g_utf8_strlen (normal
, -1) - 1);
1132 gtk_text_iter_forward_char (iter
);
1140 lines_match (const GtkTextIter
*start
,
1141 const gchar
**lines
,
1142 gboolean visible_only
,
1144 GtkTextIter
*match_start
,
1145 GtkTextIter
*match_end
)
1152 if (*lines
== NULL
|| **lines
== '\0')
1155 *match_start
= *start
;
1157 *match_end
= *start
;
1162 gtk_text_iter_forward_line (&next
);
1164 /* No more text in buffer, but *lines is nonempty */
1165 if (gtk_text_iter_equal (start
, &next
))
1171 line_text
= gtk_text_iter_get_visible_slice (start
, &next
);
1173 line_text
= gtk_text_iter_get_slice (start
, &next
);
1178 line_text
= gtk_text_iter_get_visible_text (start
, &next
);
1180 line_text
= gtk_text_iter_get_text (start
, &next
);
1183 if (match_start
) /* if this is the first line we're matching */
1185 found
= g_utf8_strcasestr (line_text
, *lines
);
1189 /* If it's not the first line, we have to match from the
1190 * start of the line.
1192 if (g_utf8_caselessnmatch (line_text
, *lines
, strlen (line_text
),
1205 /* Get offset to start of search string */
1206 offset
= g_utf8_strlen (line_text
, found
- line_text
);
1210 /* If match start needs to be returned, set it to the
1211 * start of the search string.
1213 forward_chars_with_skipping (&next
, offset
, visible_only
, !slice
, FALSE
);
1216 *match_start
= next
;
1219 /* Go to end of search string */
1220 forward_chars_with_skipping (&next
, g_utf8_strlen (*lines
, -1), visible_only
, !slice
, TRUE
);
1229 /* pass NULL for match_start, since we don't need to find the
1232 return lines_match (&next
, lines
, visible_only
, slice
, NULL
, match_end
);
1235 /* strsplit () that retains the delimiter as part of the string. */
1237 strbreakup (const char *string
,
1238 const char *delimiter
,
1241 GSList
*string_list
= NULL
, *slist
;
1242 gchar
**str_array
, *s
, *casefold
, *new_string
;
1245 g_return_val_if_fail (string
!= NULL
, NULL
);
1246 g_return_val_if_fail (delimiter
!= NULL
, NULL
);
1249 max_tokens
= G_MAXINT
;
1251 s
= strstr (string
, delimiter
);
1254 guint delimiter_len
= strlen (delimiter
);
1260 len
= s
- string
+ delimiter_len
;
1261 new_string
= g_new (gchar
, len
+ 1);
1262 strncpy (new_string
, string
, len
);
1263 new_string
[len
] = 0;
1264 casefold
= g_utf8_casefold (new_string
, -1);
1265 g_free (new_string
);
1266 new_string
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1268 string_list
= g_slist_prepend (string_list
, new_string
);
1270 string
= s
+ delimiter_len
;
1271 s
= strstr (string
, delimiter
);
1272 } while (--max_tokens
&& s
);
1278 casefold
= g_utf8_casefold (string
, -1);
1279 new_string
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1281 string_list
= g_slist_prepend (string_list
, new_string
);
1284 str_array
= g_new (gchar
*, n
);
1288 str_array
[i
--] = NULL
;
1289 for (slist
= string_list
; slist
; slist
= slist
->next
)
1290 str_array
[i
--] = slist
->data
;
1292 g_slist_free (string_list
);
1298 empathy_text_iter_forward_search (const GtkTextIter
*iter
,
1300 GtkTextIter
*match_start
,
1301 GtkTextIter
*match_end
,
1302 const GtkTextIter
*limit
)
1304 gchar
**lines
= NULL
;
1306 gboolean retval
= FALSE
;
1308 gboolean visible_only
;
1311 g_return_val_if_fail (iter
!= NULL
, FALSE
);
1312 g_return_val_if_fail (str
!= NULL
, FALSE
);
1314 if (limit
&& gtk_text_iter_compare (iter
, limit
) >= 0)
1318 /* If we can move one char, return the empty string there */
1321 if (gtk_text_iter_forward_char (&match
)) {
1322 if (limit
&& gtk_text_iter_equal (&match
, limit
)) {
1327 *match_start
= match
;
1338 visible_only
= TRUE
;
1341 /* locate all lines */
1342 lines
= strbreakup (str
, "\n", -1);
1347 /* This loop has an inefficient worst-case, where
1348 * gtk_text_iter_get_text () is called repeatedly on
1353 if (limit
&& gtk_text_iter_compare (&search
, limit
) >= 0) {
1357 if (lines_match (&search
, (const gchar
**)lines
,
1358 visible_only
, slice
, &match
, &end
)) {
1359 if (limit
== NULL
||
1360 (limit
&& gtk_text_iter_compare (&end
, limit
) <= 0)) {
1364 *match_start
= match
;
1372 } while (gtk_text_iter_forward_line (&search
));
1374 g_strfreev ((gchar
**) lines
);
1379 static const gchar
*
1380 g_utf8_strrcasestr (const gchar
*haystack
, const gchar
*needle
)
1384 const gchar
*ret
= NULL
;
1387 gchar
*caseless_haystack
;
1390 g_return_val_if_fail (haystack
!= NULL
, NULL
);
1391 g_return_val_if_fail (needle
!= NULL
, NULL
);
1393 casefold
= g_utf8_casefold (haystack
, -1);
1394 caseless_haystack
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
1397 needle_len
= g_utf8_strlen (needle
, -1);
1398 haystack_len
= g_utf8_strlen (caseless_haystack
, -1);
1400 if (needle_len
== 0)
1402 ret
= (gchar
*) haystack
;
1406 if (haystack_len
< needle_len
)
1412 i
= haystack_len
- needle_len
;
1413 p
= g_utf8_offset_to_pointer (caseless_haystack
, i
);
1414 needle_len
= strlen (needle
);
1416 while (p
>= caseless_haystack
)
1418 if (strncmp (p
, needle
, needle_len
) == 0)
1420 ret
= pointer_from_offset_skipping_decomp (haystack
, i
);
1424 p
= g_utf8_prev_char (p
);
1429 g_free (caseless_haystack
);
1435 backward_lines_match (const GtkTextIter
*start
,
1436 const gchar
**lines
,
1437 gboolean visible_only
,
1439 GtkTextIter
*match_start
,
1440 GtkTextIter
*match_end
)
1442 GtkTextIter line
, next
;
1447 if (*lines
== NULL
|| **lines
== '\0')
1450 *match_start
= *start
;
1452 *match_end
= *start
;
1456 line
= next
= *start
;
1457 if (gtk_text_iter_get_line_offset (&next
) == 0)
1459 if (!gtk_text_iter_backward_line (&next
))
1463 gtk_text_iter_set_line_offset (&next
, 0);
1468 line_text
= gtk_text_iter_get_visible_slice (&next
, &line
);
1470 line_text
= gtk_text_iter_get_slice (&next
, &line
);
1475 line_text
= gtk_text_iter_get_visible_text (&next
, &line
);
1477 line_text
= gtk_text_iter_get_text (&next
, &line
);
1480 if (match_start
) /* if this is the first line we're matching */
1482 found
= g_utf8_strrcasestr (line_text
, *lines
);
1486 /* If it's not the first line, we have to match from the
1487 * start of the line.
1489 if (g_utf8_caselessnmatch (line_text
, *lines
, strlen (line_text
),
1502 /* Get offset to start of search string */
1503 offset
= g_utf8_strlen (line_text
, found
- line_text
);
1505 forward_chars_with_skipping (&next
, offset
, visible_only
, !slice
, FALSE
);
1507 /* If match start needs to be returned, set it to the
1508 * start of the search string.
1512 *match_start
= next
;
1515 /* Go to end of search string */
1516 forward_chars_with_skipping (&next
, g_utf8_strlen (*lines
, -1), visible_only
, !slice
, TRUE
);
1525 /* try to match the rest of the lines forward, passing NULL
1526 * for match_start so lines_match will try to match the entire
1528 return lines_match (&next
, lines
, visible_only
,
1529 slice
, NULL
, match_end
);
1533 empathy_text_iter_backward_search (const GtkTextIter
*iter
,
1535 GtkTextIter
*match_start
,
1536 GtkTextIter
*match_end
,
1537 const GtkTextIter
*limit
)
1539 gchar
**lines
= NULL
;
1541 gboolean retval
= FALSE
;
1543 gboolean visible_only
;
1546 g_return_val_if_fail (iter
!= NULL
, FALSE
);
1547 g_return_val_if_fail (str
!= NULL
, FALSE
);
1549 if (limit
&& gtk_text_iter_compare (iter
, limit
) <= 0)
1554 /* If we can move one char, return the empty string there */
1557 if (gtk_text_iter_backward_char (&match
))
1559 if (limit
&& gtk_text_iter_equal (&match
, limit
))
1563 *match_start
= match
;
1574 visible_only
= TRUE
;
1577 /* locate all lines */
1578 lines
= strbreakup (str
, "\n", -1);
1584 /* This loop has an inefficient worst-case, where
1585 * gtk_text_iter_get_text () is called repeatedly on
1590 if (limit
&& gtk_text_iter_compare (&search
, limit
) <= 0)
1593 if (backward_lines_match (&search
, (const gchar
**)lines
,
1594 visible_only
, slice
, &match
, &end
))
1596 if (limit
== NULL
|| (limit
&&
1597 gtk_text_iter_compare (&end
, limit
) > 0))
1602 *match_start
= match
;
1609 if (gtk_text_iter_get_line_offset (&search
) == 0)
1611 if (!gtk_text_iter_backward_line (&search
))
1616 gtk_text_iter_set_line_offset (&search
, 0);
1620 g_strfreev ((gchar
**) lines
);
1625 /* Takes care of moving the window to the current workspace. */
1627 empathy_window_present_with_time (GtkWindow
*window
,
1630 GdkWindow
*gdk_window
;
1632 g_return_if_fail (GTK_IS_WINDOW (window
));
1634 /* Move the window to the current workspace before trying to show it.
1635 * This is the behaviour people expect when clicking on the statusbar icon. */
1636 gdk_window
= gtk_widget_get_window (GTK_WIDGET (window
));
1641 /* Has no effect if the WM has viewports, like compiz */
1642 gdk_x11_window_move_to_current_desktop (gdk_window
);
1644 /* If window is still off-screen, hide it to force it to
1645 * reposition on the current workspace. */
1646 gtk_window_get_position (window
, &x
, &y
);
1647 gtk_window_get_size (window
, &w
, &h
);
1648 if (!EMPATHY_RECT_IS_ON_SCREEN (x
, y
, w
, h
))
1649 gtk_widget_hide (GTK_WIDGET (window
));
1652 if (timestamp
== GDK_CURRENT_TIME
)
1653 gtk_window_present (window
);
1655 gtk_window_present_with_time (window
, timestamp
);
1659 empathy_window_present (GtkWindow
*window
)
1661 empathy_window_present_with_time (window
, gtk_get_current_event_time ());
1665 empathy_get_toplevel_window (GtkWidget
*widget
)
1667 GtkWidget
*toplevel
;
1669 g_return_val_if_fail (GTK_IS_WIDGET (widget
), NULL
);
1671 toplevel
= gtk_widget_get_toplevel (widget
);
1672 if (GTK_IS_WINDOW (toplevel
) &&
1673 gtk_widget_is_toplevel (toplevel
)) {
1674 return GTK_WINDOW (toplevel
);
1680 /** empathy_make_absolute_url_len:
1684 * Same as #empathy_make_absolute_url but for a limited string length
1687 empathy_make_absolute_url_len (const gchar
*url
,
1690 g_return_val_if_fail (url
!= NULL
, NULL
);
1692 if (g_str_has_prefix (url
, "ghelp:") ||
1693 g_str_has_prefix (url
, "mailto:") ||
1694 strstr (url
, ":/")) {
1695 return g_strndup (url
, len
);
1698 if (strstr (url
, "@")) {
1699 return g_strdup_printf ("mailto:%.*s", len
, url
);
1702 return g_strdup_printf ("http://%.*s", len
, url
);
1705 /** empathy_make_absolute_url:
1708 * The URL opening code can't handle schemeless strings, so we try to be
1709 * smart and add http if there is no scheme or doesn't look like a mail
1710 * address. This should work in most cases, and let us click on strings
1711 * like "www.gnome.org".
1713 * Returns: a newly allocated url with proper mailto: or http:// prefix, use
1714 * g_free when your are done with it
1717 empathy_make_absolute_url (const gchar
*url
)
1719 return empathy_make_absolute_url_len (url
, strlen (url
));
1723 empathy_url_show (GtkWidget
*parent
,
1727 GError
*error
= NULL
;
1729 g_return_if_fail (parent
== NULL
|| GTK_IS_WIDGET (parent
));
1730 g_return_if_fail (url
!= NULL
);
1732 real_url
= empathy_make_absolute_url (url
);
1734 gtk_show_uri (parent
? gtk_widget_get_screen (parent
) : NULL
, real_url
,
1735 gtk_get_current_event_time (), &error
);
1740 dialog
= gtk_message_dialog_new (NULL
, 0,
1741 GTK_MESSAGE_ERROR
, GTK_BUTTONS_CLOSE
,
1742 _("Unable to open URI"));
1743 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog
),
1744 "%s", error
->message
);
1746 g_signal_connect (dialog
, "response",
1747 G_CALLBACK (gtk_widget_destroy
),
1749 gtk_window_present (GTK_WINDOW (dialog
));
1751 g_clear_error (&error
);
1758 empathy_send_file (EmpathyContact
*contact
, GFile
*file
)
1760 EmpathyFTFactory
*factory
;
1761 GtkRecentManager
*manager
;
1764 g_return_if_fail (EMPATHY_IS_CONTACT (contact
));
1765 g_return_if_fail (G_IS_FILE (file
));
1767 factory
= empathy_ft_factory_dup_singleton ();
1769 empathy_ft_factory_new_transfer_outgoing (factory
, contact
, file
,
1770 empathy_get_current_action_time ());
1772 uri
= g_file_get_uri (file
);
1773 manager
= gtk_recent_manager_get_default ();
1774 gtk_recent_manager_add_item (manager
, uri
);
1777 g_object_unref (factory
);
1781 empathy_send_file_from_uri_list (EmpathyContact
*contact
, const gchar
*uri_list
)
1786 /* Only handle a single file for now. It would be wicked cool to be
1787 able to do multiple files, offering to zip them or whatever like
1788 nautilus-sendto does. Note that text/uri-list is defined to have
1789 each line terminated by \r\n, but we can be tolerant of applications
1790 that only use \n or don't terminate single-line entries.
1792 nl
= strstr (uri_list
, "\r\n");
1794 nl
= strchr (uri_list
, '\n');
1797 gchar
*uri
= g_strndup (uri_list
, nl
- uri_list
);
1798 file
= g_file_new_for_uri (uri
);
1802 file
= g_file_new_for_uri (uri_list
);
1805 empathy_send_file (contact
, file
);
1807 g_object_unref (file
);
1811 file_manager_send_file_response_cb (GtkDialog
*widget
,
1813 EmpathyContact
*contact
)
1817 if (response_id
== GTK_RESPONSE_OK
) {
1818 file
= gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget
));
1820 empathy_send_file (contact
, file
);
1822 g_object_unref (file
);
1825 g_object_unref (contact
);
1826 gtk_widget_destroy (GTK_WIDGET (widget
));
1830 filter_cb (const GtkFileFilterInfo
*filter_info
,
1833 /* filter out socket files */
1834 return tp_strdiff (filter_info
->mime_type
, "inode/socket");
1837 static GtkFileFilter
*
1838 create_file_filter (void)
1840 GtkFileFilter
*filter
;
1842 filter
= gtk_file_filter_new ();
1844 gtk_file_filter_add_custom (filter
, GTK_FILE_FILTER_MIME_TYPE
, filter_cb
,
1851 empathy_send_file_with_file_chooser (EmpathyContact
*contact
)
1856 g_return_if_fail (EMPATHY_IS_CONTACT (contact
));
1858 DEBUG ("Creating selection file chooser");
1860 widget
= gtk_file_chooser_dialog_new (_("Select a file"),
1862 GTK_FILE_CHOOSER_ACTION_OPEN
,
1864 GTK_RESPONSE_CANCEL
,
1868 button
= gtk_button_new_with_mnemonic (_("_Send"));
1869 gtk_button_set_image (GTK_BUTTON (button
),
1870 gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND
,
1871 GTK_ICON_SIZE_BUTTON
));
1872 gtk_widget_show (button
);
1873 gtk_dialog_add_action_widget (GTK_DIALOG (widget
), button
,
1875 gtk_widget_set_can_default (button
, TRUE
);
1876 gtk_dialog_set_default_response (GTK_DIALOG (widget
),
1879 gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget
), FALSE
);
1881 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget
),
1884 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget
),
1885 create_file_filter ());
1887 g_signal_connect (widget
, "response",
1888 G_CALLBACK (file_manager_send_file_response_cb
),
1889 g_object_ref (contact
));
1891 gtk_widget_show (widget
);
1895 file_manager_receive_file_response_cb (GtkDialog
*dialog
,
1896 GtkResponseType response
,
1897 EmpathyFTHandler
*handler
)
1899 EmpathyFTFactory
*factory
;
1902 if (response
== GTK_RESPONSE_OK
) {
1905 guint64 free_space
, file_size
;
1906 GError
*error
= NULL
;
1908 file
= gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog
));
1909 parent
= g_file_get_parent (file
);
1910 info
= g_file_query_filesystem_info (parent
,
1911 G_FILE_ATTRIBUTE_FILESYSTEM_FREE
,
1914 g_object_unref (parent
);
1916 if (error
!= NULL
) {
1917 g_warning ("Error: %s", error
->message
);
1919 g_object_unref (file
);
1923 free_space
= g_file_info_get_attribute_uint64 (info
,
1924 G_FILE_ATTRIBUTE_FILESYSTEM_FREE
);
1925 file_size
= empathy_ft_handler_get_total_bytes (handler
);
1927 g_object_unref (info
);
1929 if (file_size
> free_space
) {
1930 GtkWidget
*message
= gtk_message_dialog_new (
1931 GTK_WINDOW (dialog
),
1935 _("Insufficient free space to save file"));
1936 char *file_size_str
, *free_space_str
;
1938 file_size_str
= g_format_size (file_size
);
1939 free_space_str
= g_format_size (free_space
);
1941 gtk_message_dialog_format_secondary_text (
1942 GTK_MESSAGE_DIALOG (message
),
1943 _("%s of free space are required to save this "
1944 "file, but only %s is available. Please "
1945 "choose another location."),
1946 file_size_str
, free_space_str
);
1948 gtk_dialog_run (GTK_DIALOG (message
));
1950 g_free (file_size_str
);
1951 g_free (free_space_str
);
1952 gtk_widget_destroy (message
);
1954 g_object_unref (file
);
1959 factory
= empathy_ft_factory_dup_singleton ();
1961 empathy_ft_factory_set_destination_for_incoming_handler (
1962 factory
, handler
, file
);
1964 g_object_unref (factory
);
1965 g_object_unref (file
);
1967 /* unref the handler, as we dismissed the file chooser,
1968 * and refused the transfer.
1970 g_object_unref (handler
);
1973 gtk_widget_destroy (GTK_WIDGET (dialog
));
1977 empathy_receive_file_with_file_chooser (EmpathyFTHandler
*handler
)
1981 EmpathyContact
*contact
;
1984 contact
= empathy_ft_handler_get_contact (handler
);
1985 g_assert (contact
!= NULL
);
1987 title
= g_strdup_printf (_("Incoming file from %s"),
1988 empathy_contact_get_alias (contact
));
1990 widget
= gtk_file_chooser_dialog_new (title
,
1992 GTK_FILE_CHOOSER_ACTION_SAVE
,
1994 GTK_RESPONSE_CANCEL
,
1998 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget
),
1999 empathy_ft_handler_get_filename (handler
));
2000 gtk_file_chooser_set_do_overwrite_confirmation
2001 (GTK_FILE_CHOOSER (widget
), TRUE
);
2003 dir
= g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD
);
2005 /* Fallback to $HOME if $XDG_DOWNLOAD_DIR is not set */
2006 dir
= g_get_home_dir ();
2008 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget
), dir
);
2010 g_signal_connect (widget
, "response",
2011 G_CALLBACK (file_manager_receive_file_response_cb
), handler
);
2013 gtk_widget_show (widget
);
2018 empathy_make_color_whiter (GdkRGBA
*color
)
2020 const GdkRGBA white
= { 1.0, 1.0, 1.0, 1.0 };
2022 color
->red
= (color
->red
+ white
.red
) / 2;
2023 color
->green
= (color
->green
+ white
.green
) / 2;
2024 color
->blue
= (color
->blue
+ white
.blue
) / 2;
2028 menu_deactivate_cb (GtkMenu
*menu
,
2031 /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
2032 g_signal_handlers_disconnect_by_func (menu
,
2033 menu_deactivate_cb
, user_data
);
2035 gtk_menu_detach (menu
);
2038 /* Convenient function to create a GtkMenu attached to @attach_to and detach
2039 * it when the menu is not displayed any more. This is useful when creating a
2040 * context menu that we want to get rid as soon as it as been displayed. */
2042 empathy_context_menu_new (GtkWidget
*attach_to
)
2046 menu
= gtk_menu_new ();
2048 gtk_menu_attach_to_widget (GTK_MENU (menu
), attach_to
, NULL
);
2050 /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
2051 * floating ref. We can either wait that @attach_to releases its ref when
2052 * it will be destroyed (when leaving Empathy most of the time) or explicitely
2053 * detach the menu when it's not displayed any more.
2054 * We go for the latter as we don't want to keep useless menus in memory
2055 * during the whole lifetime of Empathy. */
2056 g_signal_connect (menu
, "deactivate", G_CALLBACK (menu_deactivate_cb
), NULL
);
2062 empathy_get_current_action_time (void)
2064 return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
2067 /* @words = empathy_live_search_strip_utf8_string (@text);
2069 * User has to pass both so we don't have to compute @words ourself each time
2070 * this function is called. */
2072 empathy_individual_match_string (FolksIndividual
*individual
,
2079 gboolean retval
= FALSE
;
2081 /* check alias name */
2082 str
= folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual
));
2084 if (empathy_live_search_match_words (str
, words
))
2087 personas
= folks_individual_get_personas (individual
);
2089 /* check contact id, remove the @server.com part */
2090 iter
= gee_iterable_iterator (GEE_ITERABLE (personas
));
2091 while (retval
== FALSE
&& gee_iterator_next (iter
))
2093 FolksPersona
*persona
= gee_iterator_get (iter
);
2096 if (empathy_folks_persona_is_interesting (persona
))
2098 str
= folks_persona_get_display_id (persona
);
2100 /* Accept the persona if @text is a full prefix of his ID; that allows
2101 * user to find, say, a jabber contact by typing his JID. */
2102 if (g_str_has_prefix (str
, text
))
2108 gchar
*dup_str
= NULL
;
2111 p
= strstr (str
, "@");
2113 str
= dup_str
= g_strndup (str
, p
- str
);
2115 visible
= empathy_live_search_match_words (str
, words
);
2121 g_clear_object (&persona
);
2123 g_clear_object (&iter
);
2125 /* FIXME: Add more rules here, we could check phone numbers in
2126 * contact's vCard for example. */
2131 empathy_launch_program (const gchar
*dir
,
2135 GdkDisplay
*display
;
2136 GError
*error
= NULL
;
2139 GdkAppLaunchContext
*context
= NULL
;
2141 /* Try to run from source directory if possible */
2142 path
= g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
2145 if (!g_file_test (path
, G_FILE_TEST_EXISTS
))
2148 path
= g_build_filename (dir
, name
, NULL
);
2152 cmd
= g_strconcat (path
, " ", args
, NULL
);
2154 cmd
= g_strdup (path
);
2156 app_info
= g_app_info_create_from_commandline (cmd
, NULL
, 0, &error
);
2157 if (app_info
== NULL
)
2159 DEBUG ("Failed to create app info: %s", error
->message
);
2160 g_error_free (error
);
2164 display
= gdk_display_get_default ();
2165 context
= gdk_display_get_app_launch_context (display
);
2167 if (!g_app_info_launch (app_info
, NULL
, (GAppLaunchContext
*) context
,
2170 g_warning ("Failed to launch %s: %s", name
, error
->message
);
2171 g_error_free (error
);
2176 tp_clear_object (&app_info
);
2177 tp_clear_object (&context
);
2182 /* Most of the workspace manipulation code has been copied from libwnck
2183 * Copyright (C) 2001 Havoc Pennington
2184 * Copyright (C) 2005-2007 Vincent Untz
2187 _wnck_activate_workspace (Screen
*screen
,
2188 int new_active_space
,
2195 display
= DisplayOfScreen (screen
);
2196 root
= RootWindowOfScreen (screen
);
2198 xev
.xclient
.type
= ClientMessage
;
2199 xev
.xclient
.serial
= 0;
2200 xev
.xclient
.send_event
= True
;
2201 xev
.xclient
.display
= display
;
2202 xev
.xclient
.window
= root
;
2203 xev
.xclient
.message_type
= gdk_x11_get_xatom_by_name ("_NET_CURRENT_DESKTOP");
2204 xev
.xclient
.format
= 32;
2205 xev
.xclient
.data
.l
[0] = new_active_space
;
2206 xev
.xclient
.data
.l
[1] = timestamp
;
2207 xev
.xclient
.data
.l
[2] = 0;
2208 xev
.xclient
.data
.l
[3] = 0;
2209 xev
.xclient
.data
.l
[4] = 0;
2211 gdk_error_trap_push ();
2212 XSendEvent (display
, root
, False
,
2213 SubstructureRedirectMask
| SubstructureNotifyMask
,
2215 XSync (display
, False
);
2216 gdk_error_trap_pop_ignored ();
2220 _wnck_get_cardinal (Screen
*screen
,
2233 display
= DisplayOfScreen (screen
);
2237 gdk_error_trap_push ();
2239 result
= XGetWindowProperty (display
, xwindow
, atom
,
2240 0, G_MAXLONG
, False
, XA_CARDINAL
, &type
, &format
, &nitems
,
2241 &bytes_after
, (void *) &num
);
2242 err
= gdk_error_trap_pop ();
2243 if (err
!= Success
||
2247 if (type
!= XA_CARDINAL
)
2261 window_get_workspace (Screen
*xscreen
,
2266 if (!_wnck_get_cardinal (xscreen
, win
,
2267 gdk_x11_get_xatom_by_name ("_NET_WM_DESKTOP"), &number
))
2273 /* Ask X to move to the desktop on which @window currently is
2274 * and the present @window. */
2276 empathy_move_to_window_desktop (GtkWindow
*window
,
2281 GdkWindow
*gdk_window
;
2284 screen
= gtk_window_get_screen (window
);
2285 xscreen
= gdk_x11_screen_get_xscreen (screen
);
2286 gdk_window
= gtk_widget_get_window (GTK_WIDGET (window
));
2288 workspace
= window_get_workspace (xscreen
,
2289 gdk_x11_window_get_xid (gdk_window
));
2290 if (workspace
== -1)
2293 _wnck_activate_workspace (xscreen
, workspace
, timestamp
);
2296 gtk_window_present_with_time (window
, timestamp
);