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-2008 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., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, 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>
27 * Part of this file is copied from GtkSourceView (gtksourceiter.c):
35 #include <X11/Xatom.h>
37 #include <glib/gi18n-lib.h>
40 #include <canberra-gtk.h>
42 #include <libmissioncontrol/mc-profile.h>
44 #include "empathy-ui-utils.h"
45 #include "empathy-images.h"
46 #include "empathy-conf.h"
48 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
49 #include <libempathy/empathy-debug.h>
50 #include <libempathy/empathy-utils.h>
51 #include <libempathy/empathy-dispatcher.h>
52 #include <libempathy/empathy-idle.h>
53 #include <libempathy/empathy-tp-file.h>
55 #define SCHEMES "(https?|s?ftps?|nntp|news|javascript|about|ghelp|apt|telnet|"\
57 #define BODY "([^\\ \\n]+)"
58 #define END_BODY "([^\\ \\n]*[^,;\?><()\\ \"\\.\\n])"
59 #define URI_REGEX "("SCHEMES"://"END_BODY")" \
60 "|((mailto:)?"BODY"@"BODY"\\."END_BODY")"\
61 "|((www|ftp)\\."END_BODY")"
64 empathy_gtk_init (void)
66 static gboolean initialized
= FALSE
;
72 gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
73 PKGDATADIR G_DIR_SEPARATOR_S
"icons");
79 empathy_uri_regex_dup_singleton (void)
81 static GRegex
*uri_regex
= NULL
;
83 /* We intentionally leak the regex so it's not recomputed */
85 uri_regex
= g_regex_new (URI_REGEX
, 0, 0, NULL
);
88 return g_regex_ref (uri_regex
);
92 builder_get_file_valist (const gchar
*filename
,
93 const gchar
*first_object
,
101 DEBUG ("Loading file %s", filename
);
103 gui
= gtk_builder_new ();
104 if (!gtk_builder_add_from_file (gui
, filename
, &error
)) {
105 DEBUG ("Error: %s", error
->message
);
106 g_clear_error (&error
);
107 g_object_unref (gui
);
111 for (name
= first_object
; name
; name
= va_arg (args
, const gchar
*)) {
112 object_ptr
= va_arg (args
, GObject
**);
114 *object_ptr
= gtk_builder_get_object (gui
, name
);
117 g_warning ("File is missing object '%s'.", name
);
126 empathy_builder_get_file (const gchar
*filename
,
127 const gchar
*first_object
,
133 va_start (args
, first_object
);
134 gui
= builder_get_file_valist (filename
, first_object
, args
);
141 empathy_builder_connect (GtkBuilder
*gui
,
152 va_start (args
, first_object
);
153 for (name
= first_object
; name
; name
= va_arg (args
, const gchar
*)) {
154 signal
= va_arg (args
, const gchar
*);
155 callback
= va_arg (args
, GCallback
);
157 object
= gtk_builder_get_object (gui
, name
);
159 g_warning ("File is missing object '%s'.", name
);
163 g_signal_connect (object
, signal
, callback
, user_data
);
170 empathy_builder_unref_and_keep_widget (GtkBuilder
*gui
,
173 /* On construction gui sinks the initial reference to widget. When gui
174 * is finalized it will drop its ref to widget. We take our own ref to
175 * prevent widget being finalised. The widget is forced to have a
176 * floating reference, like when it was initially unowned so that it can
177 * be used like any other GtkWidget. */
179 g_object_ref (widget
);
180 g_object_force_floating (G_OBJECT (widget
));
181 g_object_unref (gui
);
187 empathy_icon_name_from_account (McAccount
*account
)
191 profile
= mc_account_get_profile (account
);
193 return mc_profile_get_icon_name (profile
);
197 empathy_icon_name_for_presence (McPresence presence
)
200 case MC_PRESENCE_AVAILABLE
:
201 return EMPATHY_IMAGE_AVAILABLE
;
202 case MC_PRESENCE_DO_NOT_DISTURB
:
203 return EMPATHY_IMAGE_BUSY
;
204 case MC_PRESENCE_AWAY
:
205 return EMPATHY_IMAGE_AWAY
;
206 case MC_PRESENCE_EXTENDED_AWAY
:
207 return EMPATHY_IMAGE_EXT_AWAY
;
208 case MC_PRESENCE_HIDDEN
:
209 return EMPATHY_IMAGE_HIDDEN
;
210 case MC_PRESENCE_OFFLINE
:
211 case MC_PRESENCE_UNSET
:
212 return EMPATHY_IMAGE_OFFLINE
;
214 g_assert_not_reached ();
221 empathy_icon_name_for_contact (EmpathyContact
*contact
)
225 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
),
226 EMPATHY_IMAGE_OFFLINE
);
228 presence
= empathy_contact_get_presence (contact
);
229 return empathy_icon_name_for_presence (presence
);
233 empathy_pixbuf_from_data (gchar
*data
,
236 return empathy_pixbuf_from_data_and_mime (data
, data_size
, NULL
);
240 empathy_pixbuf_from_data_and_mime (gchar
*data
,
244 GdkPixbufLoader
*loader
;
245 GdkPixbufFormat
*format
;
246 GdkPixbuf
*pixbuf
= NULL
;
248 GError
*error
= NULL
;
254 loader
= gdk_pixbuf_loader_new ();
255 if (!gdk_pixbuf_loader_write (loader
, data
, data_size
, &error
)) {
256 DEBUG ("Failed to write to pixbuf loader: %s",
257 error
? error
->message
: "No error given");
260 if (!gdk_pixbuf_loader_close (loader
, &error
)) {
261 DEBUG ("Failed to close pixbuf loader: %s",
262 error
? error
->message
: "No error given");
266 pixbuf
= gdk_pixbuf_loader_get_pixbuf (loader
);
268 g_object_ref (pixbuf
);
270 if (mime_type
!= NULL
) {
271 format
= gdk_pixbuf_loader_get_format (loader
);
272 mime_types
= gdk_pixbuf_format_get_mime_types (format
);
274 *mime_type
= g_strdup (*mime_types
);
275 if (mime_types
[1] != NULL
) {
276 DEBUG ("Loader supports more than one mime "
277 "type! Picking the first one, %s",
280 g_strfreev (mime_types
);
285 g_clear_error (&error
);
286 g_object_unref (loader
);
294 gboolean preserve_aspect_ratio
;
298 pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader
*loader
,
301 struct SizeData
*data
)
303 g_return_if_fail (width
> 0 && height
> 0);
305 if (data
->preserve_aspect_ratio
&& (data
->width
> 0 || data
->height
> 0)) {
306 if (width
< data
->width
&& height
< data
->height
) {
311 if (data
->width
< 0) {
312 width
= width
* (double) data
->height
/ (gdouble
) height
;
313 height
= data
->height
;
314 } else if (data
->height
< 0) {
315 height
= height
* (double) data
->width
/ (double) width
;
317 } else if ((double) height
* (double) data
->width
>
318 (double) width
* (double) data
->height
) {
319 width
= 0.5 + (double) width
* (double) data
->height
/ (double) height
;
320 height
= data
->height
;
322 height
= 0.5 + (double) height
* (double) data
->width
/ (double) width
;
326 if (data
->width
> 0) {
330 if (data
->height
> 0) {
331 height
= data
->height
;
335 gdk_pixbuf_loader_set_size (loader
, width
, height
);
339 empathy_avatar_pixbuf_roundify (GdkPixbuf
*pixbuf
)
341 gint width
, height
, rowstride
;
344 width
= gdk_pixbuf_get_width (pixbuf
);
345 height
= gdk_pixbuf_get_height (pixbuf
);
346 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
347 pixels
= gdk_pixbuf_get_pixels (pixbuf
);
349 if (width
< 6 || height
< 6) {
357 pixels
[rowstride
+ 3] = 0x80;
358 pixels
[rowstride
* 2 + 3] = 0xC0;
361 pixels
[width
* 4 - 1] = 0;
362 pixels
[width
* 4 - 5] = 0x80;
363 pixels
[width
* 4 - 9] = 0xC0;
364 pixels
[rowstride
+ (width
* 4) - 1] = 0x80;
365 pixels
[(2 * rowstride
) + (width
* 4) - 1] = 0xC0;
368 pixels
[(height
- 1) * rowstride
+ 3] = 0;
369 pixels
[(height
- 1) * rowstride
+ 7] = 0x80;
370 pixels
[(height
- 1) * rowstride
+ 11] = 0xC0;
371 pixels
[(height
- 2) * rowstride
+ 3] = 0x80;
372 pixels
[(height
- 3) * rowstride
+ 3] = 0xC0;
375 pixels
[height
* rowstride
- 1] = 0;
376 pixels
[(height
- 1) * rowstride
- 1] = 0x80;
377 pixels
[(height
- 2) * rowstride
- 1] = 0xC0;
378 pixels
[height
* rowstride
- 5] = 0x80;
379 pixels
[height
* rowstride
- 9] = 0xC0;
383 empathy_gdk_pixbuf_is_opaque (GdkPixbuf
*pixbuf
)
385 gint width
, height
, rowstride
, i
;
389 width
= gdk_pixbuf_get_width (pixbuf
);
390 height
= gdk_pixbuf_get_height (pixbuf
);
391 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
392 pixels
= gdk_pixbuf_get_pixels (pixbuf
);
395 for (i
= 3; i
< rowstride
; i
+=4) {
401 for (i
= 1; i
< height
- 1; i
++) {
402 row
= pixels
+ (i
*rowstride
);
403 if (row
[3] < 0xfe || row
[rowstride
-1] < 0xfe) {
408 row
= pixels
+ ((height
-1) * rowstride
);
409 for (i
= 3; i
< rowstride
; i
+=4) {
419 empathy_pixbuf_from_avatar_scaled (EmpathyAvatar
*avatar
,
424 GdkPixbufLoader
*loader
;
425 struct SizeData data
;
426 GError
*error
= NULL
;
433 data
.height
= height
;
434 data
.preserve_aspect_ratio
= TRUE
;
436 loader
= gdk_pixbuf_loader_new ();
438 g_signal_connect (loader
, "size-prepared",
439 G_CALLBACK (pixbuf_from_avatar_size_prepared_cb
),
442 if (!gdk_pixbuf_loader_write (loader
, avatar
->data
, avatar
->len
, &error
)) {
443 g_warning ("Couldn't write avatar image:%p with "
444 "length:%" G_GSIZE_FORMAT
" to pixbuf loader: %s",
445 avatar
->data
, avatar
->len
, error
->message
);
446 g_error_free (error
);
450 gdk_pixbuf_loader_close (loader
, NULL
);
452 pixbuf
= gdk_pixbuf_loader_get_pixbuf (loader
);
453 if (!gdk_pixbuf_get_has_alpha (pixbuf
)) {
454 GdkPixbuf
*rounded_pixbuf
;
456 rounded_pixbuf
= gdk_pixbuf_new (GDK_COLORSPACE_RGB
, TRUE
, 8,
457 gdk_pixbuf_get_width (pixbuf
),
458 gdk_pixbuf_get_height (pixbuf
));
459 gdk_pixbuf_copy_area (pixbuf
, 0, 0,
460 gdk_pixbuf_get_width (pixbuf
),
461 gdk_pixbuf_get_height (pixbuf
),
464 pixbuf
= rounded_pixbuf
;
466 g_object_ref (pixbuf
);
469 if (empathy_gdk_pixbuf_is_opaque (pixbuf
)) {
470 empathy_avatar_pixbuf_roundify (pixbuf
);
473 g_object_unref (loader
);
479 empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact
*contact
,
483 EmpathyAvatar
*avatar
;
485 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact
), NULL
);
487 avatar
= empathy_contact_get_avatar (contact
);
489 return empathy_pixbuf_from_avatar_scaled (avatar
, width
, height
);
493 empathy_pixbuf_scale_down_if_necessary (GdkPixbuf
*pixbuf
, gint max_size
)
498 width
= gdk_pixbuf_get_width (pixbuf
);
499 height
= gdk_pixbuf_get_height (pixbuf
);
501 if (width
> 0 && (width
> max_size
|| height
> max_size
)) {
502 factor
= (gdouble
) max_size
/ MAX (width
, height
);
504 width
= width
* factor
;
505 height
= height
* factor
;
507 return gdk_pixbuf_scale_simple (pixbuf
,
512 return g_object_ref (pixbuf
);
516 empathy_pixbuf_from_icon_name_sized (const gchar
*icon_name
,
521 GError
*error
= NULL
;
527 theme
= gtk_icon_theme_get_default ();
529 pixbuf
= gtk_icon_theme_load_icon (theme
,
535 DEBUG ("Error loading icon: %s", error
->message
);
536 g_clear_error (&error
);
543 empathy_pixbuf_from_icon_name (const gchar
*icon_name
,
544 GtkIconSize icon_size
)
553 if (gtk_icon_size_lookup (icon_size
, &w
, &h
)) {
557 return empathy_pixbuf_from_icon_name_sized (icon_name
, size
);
560 /* Stolen from GtkSourceView, hence the weird intendation. Please keep it like
561 * that to make it easier to apply changes from the original code.
563 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
565 /* this function acts like g_utf8_offset_to_pointer() except that if it finds a
566 * decomposable character it consumes the decomposition length from the given
567 * offset. So it's useful when the offset was calculated for the normalized
568 * version of str, but we need a pointer to str itself. */
570 pointer_from_offset_skipping_decomp (const gchar
*str
, gint offset
)
572 gchar
*casefold
, *normal
;
578 q
= g_utf8_next_char (p
);
579 casefold
= g_utf8_casefold (p
, q
- p
);
580 normal
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
581 offset
-= g_utf8_strlen (normal
, -1);
590 g_utf8_strcasestr (const gchar
*haystack
, const gchar
*needle
)
594 const gchar
*ret
= NULL
;
597 gchar
*caseless_haystack
;
600 g_return_val_if_fail (haystack
!= NULL
, NULL
);
601 g_return_val_if_fail (needle
!= NULL
, NULL
);
603 casefold
= g_utf8_casefold (haystack
, -1);
604 caseless_haystack
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
607 needle_len
= g_utf8_strlen (needle
, -1);
608 haystack_len
= g_utf8_strlen (caseless_haystack
, -1);
612 ret
= (gchar
*)haystack
;
616 if (haystack_len
< needle_len
)
622 p
= (gchar
*)caseless_haystack
;
623 needle_len
= strlen (needle
);
628 if ((strncmp (p
, needle
, needle_len
) == 0))
630 ret
= pointer_from_offset_skipping_decomp (haystack
, i
);
634 p
= g_utf8_next_char (p
);
639 g_free (caseless_haystack
);
645 g_utf8_caselessnmatch (const char *s1
, const char *s2
,
646 gssize n1
, gssize n2
)
649 gchar
*normalized_s1
;
650 gchar
*normalized_s2
;
653 gboolean ret
= FALSE
;
655 g_return_val_if_fail (s1
!= NULL
, FALSE
);
656 g_return_val_if_fail (s2
!= NULL
, FALSE
);
657 g_return_val_if_fail (n1
> 0, FALSE
);
658 g_return_val_if_fail (n2
> 0, FALSE
);
660 casefold
= g_utf8_casefold (s1
, n1
);
661 normalized_s1
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
664 casefold
= g_utf8_casefold (s2
, n2
);
665 normalized_s2
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
668 len_s1
= strlen (normalized_s1
);
669 len_s2
= strlen (normalized_s2
);
674 ret
= (strncmp (normalized_s1
, normalized_s2
, len_s2
) == 0);
677 g_free (normalized_s1
);
678 g_free (normalized_s2
);
684 forward_chars_with_skipping (GtkTextIter
*iter
,
686 gboolean skip_invisible
,
687 gboolean skip_nontext
,
688 gboolean skip_decomp
)
692 g_return_if_fail (count
>= 0);
698 gboolean ignored
= FALSE
;
700 /* minimal workaround to avoid the infinite loop of bug #168247.
701 * It doesn't fix the problemjust the symptom...
703 if (gtk_text_iter_is_end (iter
))
706 if (skip_nontext
&& gtk_text_iter_get_char (iter
) == GTK_TEXT_UNKNOWN_CHAR
)
709 if (!ignored
&& skip_invisible
&&
710 /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE
)
713 if (!ignored
&& skip_decomp
)
715 /* being UTF8 correct sucks; this accounts for extra
716 offsets coming from canonical decompositions of
717 UTF8 characters (e.g. accented characters) which
718 g_utf8_normalize() performs */
723 buffer_len
= g_unichar_to_utf8 (gtk_text_iter_get_char (iter
), buffer
);
724 normal
= g_utf8_normalize (buffer
, buffer_len
, G_NORMALIZE_NFD
);
725 i
-= (g_utf8_strlen (normal
, -1) - 1);
729 gtk_text_iter_forward_char (iter
);
737 lines_match (const GtkTextIter
*start
,
739 gboolean visible_only
,
741 GtkTextIter
*match_start
,
742 GtkTextIter
*match_end
)
749 if (*lines
== NULL
|| **lines
== '\0')
752 *match_start
= *start
;
759 gtk_text_iter_forward_line (&next
);
761 /* No more text in buffer, but *lines is nonempty */
762 if (gtk_text_iter_equal (start
, &next
))
768 line_text
= gtk_text_iter_get_visible_slice (start
, &next
);
770 line_text
= gtk_text_iter_get_slice (start
, &next
);
775 line_text
= gtk_text_iter_get_visible_text (start
, &next
);
777 line_text
= gtk_text_iter_get_text (start
, &next
);
780 if (match_start
) /* if this is the first line we're matching */
782 found
= g_utf8_strcasestr (line_text
, *lines
);
786 /* If it's not the first line, we have to match from the
789 if (g_utf8_caselessnmatch (line_text
, *lines
, strlen (line_text
),
802 /* Get offset to start of search string */
803 offset
= g_utf8_strlen (line_text
, found
- line_text
);
807 /* If match start needs to be returned, set it to the
808 * start of the search string.
810 forward_chars_with_skipping (&next
, offset
, visible_only
, !slice
, FALSE
);
816 /* Go to end of search string */
817 forward_chars_with_skipping (&next
, g_utf8_strlen (*lines
, -1), visible_only
, !slice
, TRUE
);
826 /* pass NULL for match_start, since we don't need to find the
829 return lines_match (&next
, lines
, visible_only
, slice
, NULL
, match_end
);
832 /* strsplit () that retains the delimiter as part of the string. */
834 strbreakup (const char *string
,
835 const char *delimiter
,
838 GSList
*string_list
= NULL
, *slist
;
839 gchar
**str_array
, *s
, *casefold
, *new_string
;
842 g_return_val_if_fail (string
!= NULL
, NULL
);
843 g_return_val_if_fail (delimiter
!= NULL
, NULL
);
846 max_tokens
= G_MAXINT
;
848 s
= strstr (string
, delimiter
);
851 guint delimiter_len
= strlen (delimiter
);
857 len
= s
- string
+ delimiter_len
;
858 new_string
= g_new (gchar
, len
+ 1);
859 strncpy (new_string
, string
, len
);
861 casefold
= g_utf8_casefold (new_string
, -1);
863 new_string
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
865 string_list
= g_slist_prepend (string_list
, new_string
);
867 string
= s
+ delimiter_len
;
868 s
= strstr (string
, delimiter
);
869 } while (--max_tokens
&& s
);
875 casefold
= g_utf8_casefold (string
, -1);
876 new_string
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
878 string_list
= g_slist_prepend (string_list
, new_string
);
881 str_array
= g_new (gchar
*, n
);
885 str_array
[i
--] = NULL
;
886 for (slist
= string_list
; slist
; slist
= slist
->next
)
887 str_array
[i
--] = slist
->data
;
889 g_slist_free (string_list
);
895 empathy_text_iter_forward_search (const GtkTextIter
*iter
,
897 GtkTextIter
*match_start
,
898 GtkTextIter
*match_end
,
899 const GtkTextIter
*limit
)
901 gchar
**lines
= NULL
;
903 gboolean retval
= FALSE
;
905 gboolean visible_only
;
908 g_return_val_if_fail (iter
!= NULL
, FALSE
);
909 g_return_val_if_fail (str
!= NULL
, FALSE
);
911 if (limit
&& gtk_text_iter_compare (iter
, limit
) >= 0)
915 /* If we can move one char, return the empty string there */
918 if (gtk_text_iter_forward_char (&match
)) {
919 if (limit
&& gtk_text_iter_equal (&match
, limit
)) {
924 *match_start
= match
;
938 /* locate all lines */
939 lines
= strbreakup (str
, "\n", -1);
944 /* This loop has an inefficient worst-case, where
945 * gtk_text_iter_get_text () is called repeatedly on
950 if (limit
&& gtk_text_iter_compare (&search
, limit
) >= 0) {
954 if (lines_match (&search
, (const gchar
**)lines
,
955 visible_only
, slice
, &match
, &end
)) {
957 (limit
&& gtk_text_iter_compare (&end
, limit
) <= 0)) {
961 *match_start
= match
;
969 } while (gtk_text_iter_forward_line (&search
));
971 g_strfreev ((gchar
**)lines
);
977 g_utf8_strrcasestr (const gchar
*haystack
, const gchar
*needle
)
981 const gchar
*ret
= NULL
;
984 gchar
*caseless_haystack
;
987 g_return_val_if_fail (haystack
!= NULL
, NULL
);
988 g_return_val_if_fail (needle
!= NULL
, NULL
);
990 casefold
= g_utf8_casefold (haystack
, -1);
991 caseless_haystack
= g_utf8_normalize (casefold
, -1, G_NORMALIZE_NFD
);
994 needle_len
= g_utf8_strlen (needle
, -1);
995 haystack_len
= g_utf8_strlen (caseless_haystack
, -1);
999 ret
= (gchar
*)haystack
;
1003 if (haystack_len
< needle_len
)
1009 i
= haystack_len
- needle_len
;
1010 p
= g_utf8_offset_to_pointer (caseless_haystack
, i
);
1011 needle_len
= strlen (needle
);
1013 while (p
>= caseless_haystack
)
1015 if (strncmp (p
, needle
, needle_len
) == 0)
1017 ret
= pointer_from_offset_skipping_decomp (haystack
, i
);
1021 p
= g_utf8_prev_char (p
);
1026 g_free (caseless_haystack
);
1032 backward_lines_match (const GtkTextIter
*start
,
1033 const gchar
**lines
,
1034 gboolean visible_only
,
1036 GtkTextIter
*match_start
,
1037 GtkTextIter
*match_end
)
1039 GtkTextIter line
, next
;
1044 if (*lines
== NULL
|| **lines
== '\0')
1047 *match_start
= *start
;
1049 *match_end
= *start
;
1053 line
= next
= *start
;
1054 if (gtk_text_iter_get_line_offset (&next
) == 0)
1056 if (!gtk_text_iter_backward_line (&next
))
1060 gtk_text_iter_set_line_offset (&next
, 0);
1065 line_text
= gtk_text_iter_get_visible_slice (&next
, &line
);
1067 line_text
= gtk_text_iter_get_slice (&next
, &line
);
1072 line_text
= gtk_text_iter_get_visible_text (&next
, &line
);
1074 line_text
= gtk_text_iter_get_text (&next
, &line
);
1077 if (match_start
) /* if this is the first line we're matching */
1079 found
= g_utf8_strrcasestr (line_text
, *lines
);
1083 /* If it's not the first line, we have to match from the
1084 * start of the line.
1086 if (g_utf8_caselessnmatch (line_text
, *lines
, strlen (line_text
),
1099 /* Get offset to start of search string */
1100 offset
= g_utf8_strlen (line_text
, found
- line_text
);
1102 forward_chars_with_skipping (&next
, offset
, visible_only
, !slice
, FALSE
);
1104 /* If match start needs to be returned, set it to the
1105 * start of the search string.
1109 *match_start
= next
;
1112 /* Go to end of search string */
1113 forward_chars_with_skipping (&next
, g_utf8_strlen (*lines
, -1), visible_only
, !slice
, TRUE
);
1122 /* try to match the rest of the lines forward, passing NULL
1123 * for match_start so lines_match will try to match the entire
1125 return lines_match (&next
, lines
, visible_only
,
1126 slice
, NULL
, match_end
);
1130 empathy_text_iter_backward_search (const GtkTextIter
*iter
,
1132 GtkTextIter
*match_start
,
1133 GtkTextIter
*match_end
,
1134 const GtkTextIter
*limit
)
1136 gchar
**lines
= NULL
;
1138 gboolean retval
= FALSE
;
1140 gboolean visible_only
;
1143 g_return_val_if_fail (iter
!= NULL
, FALSE
);
1144 g_return_val_if_fail (str
!= NULL
, FALSE
);
1146 if (limit
&& gtk_text_iter_compare (iter
, limit
) <= 0)
1151 /* If we can move one char, return the empty string there */
1154 if (gtk_text_iter_backward_char (&match
))
1156 if (limit
&& gtk_text_iter_equal (&match
, limit
))
1160 *match_start
= match
;
1171 visible_only
= TRUE
;
1174 /* locate all lines */
1175 lines
= strbreakup (str
, "\n", -1);
1181 /* This loop has an inefficient worst-case, where
1182 * gtk_text_iter_get_text () is called repeatedly on
1187 if (limit
&& gtk_text_iter_compare (&search
, limit
) <= 0)
1190 if (backward_lines_match (&search
, (const gchar
**)lines
,
1191 visible_only
, slice
, &match
, &end
))
1193 if (limit
== NULL
|| (limit
&&
1194 gtk_text_iter_compare (&end
, limit
) > 0))
1199 *match_start
= match
;
1206 if (gtk_text_iter_get_line_offset (&search
) == 0)
1208 if (!gtk_text_iter_backward_line (&search
))
1213 gtk_text_iter_set_line_offset (&search
, 0);
1217 g_strfreev ((gchar
**)lines
);
1223 empathy_window_get_is_visible (GtkWindow
*window
)
1225 GdkWindowState state
;
1226 GdkWindow
*gdk_window
;
1228 g_return_val_if_fail (GTK_IS_WINDOW (window
), FALSE
);
1230 gdk_window
= GTK_WIDGET (window
)->window
;
1235 state
= gdk_window_get_state (gdk_window
);
1236 if (state
& (GDK_WINDOW_STATE_WITHDRAWN
| GDK_WINDOW_STATE_ICONIFIED
)) {
1244 empathy_window_iconify (GtkWindow
*window
, GtkStatusIcon
*status_icon
)
1246 GdkRectangle icon_location
;
1249 GdkWindow
*gdk_window
;
1251 gtk_status_icon_get_geometry (status_icon
, NULL
, &icon_location
, NULL
);
1252 gdk_window
= GTK_WIDGET (window
)->window
;
1253 dpy
= gdk_x11_drawable_get_xdisplay (gdk_window
);
1255 data
[0] = icon_location
.x
;
1256 data
[1] = icon_location
.y
;
1257 data
[2] = icon_location
.width
;
1258 data
[3] = icon_location
.height
;
1260 XChangeProperty (dpy
,
1261 GDK_WINDOW_XID (gdk_window
),
1262 gdk_x11_get_xatom_by_name_for_display (gdk_drawable_get_display (gdk_window
),
1263 "_NET_WM_ICON_GEOMETRY"),
1264 XA_CARDINAL
, 32, PropModeReplace
,
1265 (guchar
*)&data
, 4);
1267 gtk_window_set_skip_taskbar_hint (window
, TRUE
);
1268 gtk_window_iconify (window
);
1271 /* Takes care of moving the window to the current workspace. */
1273 empathy_window_present (GtkWindow
*window
,
1274 gboolean steal_focus
)
1278 g_return_if_fail (GTK_IS_WINDOW (window
));
1280 /* There are three cases: hidden, visible, visible on another
1284 if (!empathy_window_get_is_visible (window
)) {
1285 /* Hide it so present brings it to the current workspace. */
1286 gtk_widget_hide (GTK_WIDGET (window
));
1289 timestamp
= gtk_get_current_event_time ();
1290 gtk_window_set_skip_taskbar_hint (window
, FALSE
);
1291 gtk_window_present_with_time (window
, timestamp
);
1292 /* FIXME: This shouldn't be required as gtk_window_present's doc says
1293 * it deiconify automatically. */
1294 gtk_window_deiconify (window
);
1298 empathy_get_toplevel_window (GtkWidget
*widget
)
1300 GtkWidget
*toplevel
;
1302 g_return_val_if_fail (GTK_IS_WIDGET (widget
), NULL
);
1304 toplevel
= gtk_widget_get_toplevel (widget
);
1305 if (GTK_IS_WINDOW (toplevel
) &&
1306 GTK_WIDGET_TOPLEVEL (toplevel
)) {
1307 return GTK_WINDOW (toplevel
);
1313 /* The URL opening code can't handle schemeless strings, so we try to be
1314 * smart and add http if there is no scheme or doesn't look like a mail
1315 * address. This should work in most cases, and let us click on strings
1316 * like "www.gnome.org".
1319 fixup_url (const gchar
*url
)
1321 if (g_str_has_prefix (url
, "ghelp:") ||
1322 g_str_has_prefix (url
, "mailto:") ||
1323 strstr (url
, ":/")) {
1327 if (strstr (url
, "@")) {
1328 return g_strdup_printf ("mailto:%s", url
);
1331 return g_strdup_printf ("http://%s", url
);
1335 empathy_url_show (GtkWidget
*parent
,
1339 GError
*error
= NULL
;
1341 real_url
= fixup_url (url
);
1346 gtk_show_uri (gtk_widget_get_screen (parent
), url
,
1347 gtk_get_current_event_time (), &error
);
1352 dialog
= gtk_message_dialog_new (NULL
, 0,
1353 GTK_MESSAGE_ERROR
, GTK_BUTTONS_CLOSE
,
1354 _("Unable to open URI"));
1355 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog
),
1356 "%s", error
->message
);
1358 g_signal_connect (dialog
, "response",
1359 G_CALLBACK (gtk_widget_destroy
),
1361 gtk_window_present (GTK_WINDOW (dialog
));
1363 g_clear_error (&error
);
1370 link_button_hook (GtkLinkButton
*button
,
1374 empathy_url_show (GTK_WIDGET (button
), link
);
1378 empathy_link_button_new (const gchar
*url
,
1381 static gboolean hook
= FALSE
;
1385 gtk_link_button_set_uri_hook (link_button_hook
, NULL
, NULL
);
1388 return gtk_link_button_new_with_label (url
, title
);
1392 empathy_toggle_button_set_state_quietly (GtkWidget
*widget
,
1397 g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget
));
1399 g_signal_handlers_block_by_func (widget
, callback
, user_data
);
1400 g_object_set (widget
, "active", active
, NULL
);
1401 g_signal_handlers_unblock_by_func (widget
, callback
, user_data
);
1404 /* Sending files with the file chooser */
1407 file_manager_send_file_request_cb (EmpathyDispatchOperation
*operation
,
1408 const GError
*error
, gpointer user_data
)
1410 GFile
*file
= (GFile
*)user_data
;
1411 EmpathyTpFile
*tp_file
;
1413 if (error
!= NULL
) {
1414 DEBUG ("Couldn't request channel: %s", error
->message
);
1415 g_object_unref (file
);
1419 DEBUG ("Starting to send file");
1421 tp_file
= EMPATHY_TP_FILE (
1422 empathy_dispatch_operation_get_channel_wrapper (operation
));
1424 empathy_tp_file_offer (tp_file
, file
, NULL
);
1426 g_object_unref (file
);
1430 file_manager_send_file_response_cb (GtkDialog
*widget
,
1432 EmpathyContact
*contact
)
1434 if (response_id
== GTK_RESPONSE_OK
) {
1438 list
= gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (widget
));
1440 DEBUG ("File chooser selected files:");
1442 for (l
= list
; l
; l
= l
->next
) {
1446 GtkRecentManager
*manager
;
1449 GError
*error
= NULL
;
1452 gfile
= g_file_new_for_uri (uri
);
1453 info
= g_file_query_info (gfile
,
1454 G_FILE_ATTRIBUTE_STANDARD_SIZE
","
1455 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
","
1456 G_FILE_ATTRIBUTE_TIME_MODIFIED
,
1460 DEBUG ("Can't get info about the file: %s", error
->message
);
1461 g_clear_error (&error
);
1462 g_object_unref (gfile
);
1466 DEBUG ("\t%s", uri
);
1467 filename
= g_file_get_basename (gfile
);
1468 g_file_info_get_modification_time (info
, &mtime
);
1470 empathy_dispatcher_send_file_to_contact (contact
,
1471 filename
, g_file_info_get_size (info
), mtime
.tv_sec
,
1472 g_file_info_get_content_type (info
),
1473 file_manager_send_file_request_cb
, gfile
);
1476 g_object_unref (info
);
1478 manager
= gtk_recent_manager_get_default ();
1479 gtk_recent_manager_add_item (manager
, uri
);
1483 g_slist_free (list
);
1486 gtk_widget_destroy (GTK_WIDGET (widget
));
1490 empathy_send_file_with_file_chooser (EmpathyContact
*contact
)
1495 g_return_if_fail (EMPATHY_IS_CONTACT (contact
));
1497 DEBUG ("Creating selection file chooser");
1499 widget
= gtk_file_chooser_dialog_new (_("Select a file"),
1501 GTK_FILE_CHOOSER_ACTION_OPEN
,
1503 GTK_RESPONSE_CANCEL
,
1507 button
= gtk_button_new_with_mnemonic (_("_Send"));
1508 gtk_button_set_image (GTK_BUTTON (button
),
1509 gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND
,
1510 GTK_ICON_SIZE_BUTTON
));
1511 gtk_widget_show (button
);
1512 gtk_dialog_add_action_widget (GTK_DIALOG (widget
), button
,
1514 GTK_WIDGET_SET_FLAGS (button
, GTK_CAN_DEFAULT
);
1515 gtk_dialog_set_default_response (GTK_DIALOG (widget
),
1518 g_signal_connect (widget
, "response",
1519 G_CALLBACK (file_manager_send_file_response_cb
),
1522 gtk_widget_show (widget
);
1526 EmpathySound sound_id
;
1527 const char * event_ca_id
;
1528 const char * event_ca_description
;
1529 const char * gconf_key
;
1530 } EmpathySoundEntry
;
1532 /* NOTE: these entries MUST be in the same order than EmpathySound enum */
1533 static EmpathySoundEntry sound_entries
[LAST_EMPATHY_SOUND
] = {
1534 { EMPATHY_SOUND_MESSAGE_INCOMING
, "message-new-instant",
1535 N_("Received an instant message"), EMPATHY_PREFS_SOUNDS_INCOMING_MESSAGE
} ,
1536 { EMPATHY_SOUND_MESSAGE_OUTGOING
, "message-sent-instant",
1537 N_("Sent an instant message"), EMPATHY_PREFS_SOUNDS_OUTGOING_MESSAGE
} ,
1538 { EMPATHY_SOUND_CONVERSATION_NEW
, "message-new-instant",
1539 N_("Incoming chat request"), EMPATHY_PREFS_SOUNDS_NEW_CONVERSATION
},
1540 { EMPATHY_SOUND_CONTACT_CONNECTED
, "service-login",
1541 N_("Contact connected"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGIN
},
1542 { EMPATHY_SOUND_CONTACT_DISCONNECTED
, "service-logout",
1543 N_("Contact disconnected"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGOUT
},
1544 { EMPATHY_SOUND_ACCOUNT_CONNECTED
, "service-login",
1545 N_("Connected to server"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGIN
},
1546 { EMPATHY_SOUND_ACCOUNT_DISCONNECTED
, "service-logout",
1547 N_("Disconnected from server"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGOUT
},
1548 { EMPATHY_SOUND_PHONE_INCOMING
, "phone-incoming-call",
1549 N_("Incoming voice call"), NULL
},
1550 { EMPATHY_SOUND_PHONE_OUTGOING
, "phone-outgoing-calling",
1551 N_("Outgoing voice call"), NULL
},
1552 { EMPATHY_SOUND_PHONE_HANGUP
, "phone-hangup",
1553 N_("Voice call ended"), NULL
},
1558 empathy_sound_pref_is_enabled (const char *key
)
1563 conf
= empathy_conf_get ();
1566 empathy_conf_get_bool (conf
, EMPATHY_PREFS_SOUNDS_ENABLED
, &res
);
1572 if (!empathy_check_available_state ()) {
1573 empathy_conf_get_bool (conf
, EMPATHY_PREFS_SOUNDS_DISABLED_AWAY
,
1580 empathy_conf_get_bool (conf
, key
, &res
);
1586 empathy_sound_stop (EmpathySound sound_id
)
1588 EmpathySoundEntry
*entry
;
1590 g_return_if_fail (sound_id
< LAST_EMPATHY_SOUND
);
1592 entry
= &(sound_entries
[sound_id
]);
1593 g_return_if_fail (entry
->sound_id
== sound_id
);
1595 ca_context_cancel (ca_gtk_context_get (), entry
->sound_id
);
1600 empathy_sound_play_full (GtkWidget
*widget
, EmpathySound sound_id
,
1601 ca_finish_callback_t callback
, gpointer user_data
)
1603 EmpathySoundEntry
*entry
;
1604 gboolean should_play
= TRUE
;
1605 ca_proplist
*p
= NULL
;
1608 g_return_val_if_fail (sound_id
< LAST_EMPATHY_SOUND
, FALSE
);
1610 entry
= &(sound_entries
[sound_id
]);
1611 g_return_val_if_fail (entry
->sound_id
== sound_id
, FALSE
);
1613 if (entry
->gconf_key
!= NULL
) {
1614 should_play
= empathy_sound_pref_is_enabled (entry
->gconf_key
);
1620 c
= ca_gtk_context_get ();
1621 ca_context_cancel (c
, entry
->sound_id
);
1623 DEBUG ("Play sound \"%s\" (%s)",
1625 entry
->event_ca_description
);
1627 if (ca_proplist_create(&p
) < 0)
1630 if (ca_proplist_sets (p
, CA_PROP_EVENT_ID
, entry
->event_ca_id
) < 0)
1633 if (ca_proplist_sets (p
, CA_PROP_EVENT_DESCRIPTION
,
1634 gettext (entry
->event_ca_id
)) < 0)
1637 if (ca_gtk_proplist_set_for_widget (p
, widget
) < 0)
1640 ca_context_play_full (ca_gtk_context_get (), entry
->sound_id
,
1641 p
, callback
, user_data
);
1643 ca_proplist_destroy (p
);
1649 ca_proplist_destroy (p
);
1655 empathy_sound_play (GtkWidget
*widget
, EmpathySound sound_id
)
1657 empathy_sound_play_full (widget
, sound_id
, NULL
, NULL
);