X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktextiter.c;h=2b41c34cfbb3b84eb03249a564bc8bc102f357b5;hb=ce0675f1fb2582717793ec29b1787039dfbf8437;hp=380778f90c8e4e483541e14b510b8dac58e9afb4;hpb=3511215730d4d09eeda4da219bce6ec61a16a4bf;p=~andy%2Fgtk diff --git a/gtk/gtktextiter.c b/gtk/gtktextiter.c index 380778f90..2b41c34cf 100644 --- a/gtk/gtktextiter.c +++ b/gtk/gtktextiter.c @@ -12,9 +12,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ /* @@ -444,6 +442,28 @@ gtk_text_iter_free (GtkTextIter *iter) g_slice_free (GtkTextIter, iter); } +/** + * gtk_text_iter_assign: + * @iter: a #GtkTextIter + * @other: another #GtkTextIter + * + * Assigns the value of @other to @iter. This function + * is not useful in applications, because iterators can be assigned + * with GtkTextIter i = j;. The + * function is used by language bindings. + * + * Since: 3.2 + **/ +void +gtk_text_iter_assign (GtkTextIter *iter, + const GtkTextIter *other) +{ + g_return_if_fail (iter != NULL); + g_return_if_fail (other != NULL); + + *iter = *other; +} + G_DEFINE_BOXED_TYPE (GtkTextIter, gtk_text_iter, gtk_text_iter_copy, gtk_text_iter_free) @@ -1717,7 +1737,7 @@ gtk_text_iter_get_bytes_in_line (const GtkTextIter *iter) /** * gtk_text_iter_get_attributes: * @iter: an iterator - * @values: a #GtkTextAttributes to be filled in + * @values: (out): a #GtkTextAttributes to be filled in * * Computes the effect of any tags applied to this spot in the * text. The @values parameter should be initialized to the default @@ -2452,7 +2472,7 @@ gtk_text_iter_forward_text_chars (GtkTextIter *iter, } /** - * gtk_text_iter_forward_text_chars: + * gtk_text_iter_backward_text_chars: * @iter: a #GtkTextIter * @count: number of chars to move * @@ -4383,7 +4403,8 @@ static void forward_chars_with_skipping (GtkTextIter *iter, gint count, gboolean skip_invisible, - gboolean skip_nontext) + gboolean skip_nontext, + gboolean skip_decomp) { gint i; @@ -4396,6 +4417,10 @@ forward_chars_with_skipping (GtkTextIter *iter, { gboolean ignored = FALSE; + /* minimal workaround to avoid the infinite loop of bug #168247. */ + if (gtk_text_iter_is_end (iter)) + return; + if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR) ignored = TRUE; @@ -4405,6 +4430,25 @@ forward_chars_with_skipping (GtkTextIter *iter, _gtk_text_btree_char_is_invisible (iter)) ignored = TRUE; + if (!ignored && skip_decomp) + { + /* being UTF8 correct sucks: this accounts for extra + offsets coming from canonical decompositions of + UTF8 characters (e.g. accented characters) which + g_utf8_normalize() performs */ + gchar *normal; + gchar *casefold; + gchar buffer[6]; + gint buffer_len; + + buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer); + casefold = g_utf8_casefold (buffer, buffer_len); + normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + i -= (g_utf8_strlen (normal, -1) - 1); + g_free (normal); + g_free (casefold); + } + gtk_text_iter_forward_char (iter); if (!ignored) @@ -4412,11 +4456,209 @@ forward_chars_with_skipping (GtkTextIter *iter, } } +static const gchar * +pointer_from_offset_skipping_decomp (const gchar *str, + gint offset) +{ + gchar *casefold, *normal; + const gchar *p, *q; + + p = str; + + while (offset > 0) + { + q = g_utf8_next_char (p); + casefold = g_utf8_casefold (p, q - p); + normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + offset -= g_utf8_strlen (normal, -1); + g_free (casefold); + g_free (normal); + p = q; + } + + return p; +} + +static gboolean +exact_prefix_cmp (const gchar *string, + const gchar *prefix, + guint prefix_len) +{ + GUnicodeType type; + + if (strncmp (string, prefix, prefix_len) != 0) + return FALSE; + if (string[prefix_len] == '\0') + return TRUE; + + type = g_unichar_type (g_utf8_get_char (string + prefix_len)); + + /* If string contains prefix, check that prefix is not followed + * by a unicode mark symbol, e.g. that trailing 'a' in prefix + * is not part of two-char a-with-hat symbol in string. */ + return type != G_UNICODE_SPACING_MARK && + type != G_UNICODE_ENCLOSING_MARK && + type != G_UNICODE_NON_SPACING_MARK; +} + +static const gchar * +utf8_strcasestr (const gchar *haystack, + const gchar *needle) +{ + gsize needle_len; + gsize haystack_len; + const gchar *ret = NULL; + gchar *p; + gchar *casefold; + gchar *caseless_haystack; + gint i; + + g_return_val_if_fail (haystack != NULL, NULL); + g_return_val_if_fail (needle != NULL, NULL); + + casefold = g_utf8_casefold (haystack, -1); + caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + needle_len = g_utf8_strlen (needle, -1); + haystack_len = g_utf8_strlen (caseless_haystack, -1); + + if (needle_len == 0) + { + ret = (gchar *)haystack; + goto finally; + } + + if (haystack_len < needle_len) + { + ret = NULL; + goto finally; + } + + p = (gchar *)caseless_haystack; + needle_len = strlen (needle); + i = 0; + + while (*p) + { + if (exact_prefix_cmp (p, needle, needle_len)) + { + ret = pointer_from_offset_skipping_decomp (haystack, i); + goto finally; + } + + p = g_utf8_next_char (p); + i++; + } + +finally: + g_free (caseless_haystack); + + return ret; +} + +static const gchar * +utf8_strrcasestr (const gchar *haystack, + const gchar *needle) +{ + gsize needle_len; + gsize haystack_len; + const gchar *ret = NULL; + gchar *p; + gchar *casefold; + gchar *caseless_haystack; + gint i; + + g_return_val_if_fail (haystack != NULL, NULL); + g_return_val_if_fail (needle != NULL, NULL); + + casefold = g_utf8_casefold (haystack, -1); + caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + needle_len = g_utf8_strlen (needle, -1); + haystack_len = g_utf8_strlen (caseless_haystack, -1); + + if (needle_len == 0) + { + ret = (gchar *)haystack; + goto finally; + } + + if (haystack_len < needle_len) + { + ret = NULL; + goto finally; + } + + i = haystack_len - needle_len; + p = g_utf8_offset_to_pointer (caseless_haystack, i); + needle_len = strlen (needle); + + while (p >= caseless_haystack) + { + if (exact_prefix_cmp (p, needle, needle_len)) + { + ret = pointer_from_offset_skipping_decomp (haystack, i); + goto finally; + } + + p = g_utf8_prev_char (p); + i--; + } + +finally: + g_free (caseless_haystack); + + return ret; +} + +/* normalizes caseless strings and returns true if @s2 matches + the start of @s1 */ +static gboolean +utf8_caselessnmatch (const gchar *s1, + const gchar *s2, + gssize n1, + gssize n2) +{ + gchar *casefold; + gchar *normalized_s1; + gchar *normalized_s2; + gint len_s1; + gint len_s2; + gboolean ret = FALSE; + + g_return_val_if_fail (s1 != NULL, FALSE); + g_return_val_if_fail (s2 != NULL, FALSE); + g_return_val_if_fail (n1 > 0, FALSE); + g_return_val_if_fail (n2 > 0, FALSE); + + casefold = g_utf8_casefold (s1, n1); + normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + casefold = g_utf8_casefold (s2, n2); + normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + len_s1 = strlen (normalized_s1); + len_s2 = strlen (normalized_s2); + + if (len_s1 >= len_s2) + ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0); + + g_free (normalized_s1); + g_free (normalized_s2); + + return ret; +} + static gboolean lines_match (const GtkTextIter *start, const gchar **lines, gboolean visible_only, gboolean slice, + gboolean case_insensitive, GtkTextIter *match_start, GtkTextIter *match_end) { @@ -4460,14 +4702,25 @@ lines_match (const GtkTextIter *start, } if (match_start) /* if this is the first line we're matching */ - found = strstr (line_text, *lines); + { + if (!case_insensitive) + found = strstr (line_text, *lines); + else + found = utf8_strcasestr (line_text, *lines); + } else { /* If it's not the first line, we have to match from the * start of the line. */ - if (strncmp (line_text, *lines, strlen (*lines)) == 0) - found = line_text; + if ((!case_insensitive && + (strncmp (line_text, *lines, strlen (*lines)) == 0)) || + (case_insensitive && + utf8_caselessnmatch (line_text, *lines, strlen (line_text), + strlen (*lines)))) + { + found = line_text; + } else found = NULL; } @@ -4486,19 +4739,14 @@ lines_match (const GtkTextIter *start, /* If match start needs to be returned, set it to the * start of the search string. */ + forward_chars_with_skipping (&next, offset, + visible_only, !slice, FALSE); if (match_start) - { - *match_start = next; - - forward_chars_with_skipping (match_start, offset, - visible_only, !slice); - } + *match_start = next; /* Go to end of search string */ - offset += g_utf8_strlen (*lines, -1); - - forward_chars_with_skipping (&next, offset, - visible_only, !slice); + forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), + visible_only, !slice, TRUE); g_free (line_text); @@ -4510,17 +4758,20 @@ lines_match (const GtkTextIter *start, /* pass NULL for match_start, since we don't need to find the * start again. */ - return lines_match (&next, lines, visible_only, slice, NULL, match_end); + return lines_match (&next, lines, visible_only, slice, case_insensitive, NULL, match_end); } /* strsplit () that retains the delimiter as part of the string. */ static gchar ** strbreakup (const char *string, const char *delimiter, - gint max_tokens) + gint max_tokens, + gint *num_strings, + gboolean case_insensitive) { GSList *string_list = NULL, *slist; gchar **str_array, *s; + gchar *casefold, *new_string; guint i, n = 1; g_return_val_if_fail (string != NULL, NULL); @@ -4537,12 +4788,20 @@ strbreakup (const char *string, do { guint len; - gchar *new_string; len = s - string + delimiter_len; new_string = g_new (gchar, len + 1); strncpy (new_string, string, len); new_string[len] = 0; + + if (case_insensitive) + { + casefold = g_utf8_casefold (new_string, -1); + g_free (new_string); + new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + } + string_list = g_slist_prepend (string_list, new_string); n++; string = s + delimiter_len; @@ -4553,7 +4812,17 @@ strbreakup (const char *string, if (*string) { n++; - string_list = g_slist_prepend (string_list, g_strdup (string)); + + if (case_insensitive) + { + casefold = g_utf8_casefold (string, -1); + new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + } + else + new_string = g_strdup (string); + + string_list = g_slist_prepend (string_list, new_string); } str_array = g_new (gchar*, n); @@ -4566,6 +4835,9 @@ strbreakup (const char *string, g_slist_free (string_list); + if (num_strings != NULL) + *num_strings = n - 1; + return str_array; } @@ -4592,6 +4864,8 @@ strbreakup (const char *string, * pixbufs or child widgets mixed inside the matched range. If these * flags are not given, the match must be exact; the special 0xFFFC * character in @str will match embedded pixbufs or child widgets. + * If you specify the #GTK_TEXT_SEARCH_CASE_INSENSITIVE flag, the text will + * be matched regardless of what case it is in. * * Return value: whether a match was found **/ @@ -4609,7 +4883,8 @@ gtk_text_iter_forward_search (const GtkTextIter *iter, GtkTextIter search; gboolean visible_only; gboolean slice; - + gboolean case_insensitive; + g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (str != NULL, FALSE); @@ -4640,10 +4915,11 @@ gtk_text_iter_forward_search (const GtkTextIter *iter, visible_only = (flags & GTK_TEXT_SEARCH_VISIBLE_ONLY) != 0; slice = (flags & GTK_TEXT_SEARCH_TEXT_ONLY) == 0; - + case_insensitive = (flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE) != 0; + /* locate all lines */ - lines = strbreakup (str, "\n", -1); + lines = strbreakup (str, "\n", -1, NULL, case_insensitive); search = *iter; @@ -4660,7 +4936,7 @@ gtk_text_iter_forward_search (const GtkTextIter *iter, break; if (lines_match (&search, (const gchar**)lines, - visible_only, slice, &match, &end)) + visible_only, slice, case_insensitive, &match, &end)) { if (limit == NULL || (limit && @@ -4686,8 +4962,9 @@ gtk_text_iter_forward_search (const GtkTextIter *iter, } static gboolean -vectors_equal_ignoring_trailing (gchar **vec1, - gchar **vec2) +vectors_equal_ignoring_trailing (gchar **vec1, + gchar **vec2, + gboolean case_insensitive) { /* Ignores trailing chars in vec2's last line */ @@ -4698,37 +4975,67 @@ vectors_equal_ignoring_trailing (gchar **vec1, while (*i1 && *i2) { - if (strcmp (*i1, *i2) != 0) + gint len1; + gint len2; + + if (!case_insensitive) { - if (*(i2 + 1) == NULL) /* if this is the last line */ + if (strcmp (*i1, *i2) != 0) { - gint len1 = strlen (*i1); - gint len2 = strlen (*i2); - - if (len2 >= len1 && - strncmp (*i1, *i2, len1) == 0) + if (*(i2 + 1) == NULL) /* if this is the last line */ { - /* We matched ignoring the trailing stuff in vec2 */ - return TRUE; + len1 = strlen (*i1); + len2 = strlen (*i2); + + if (len2 >= len1 && + strncmp (*i1, *i2, len1) == 0) + { + /* We matched ignoring the trailing stuff in vec2 */ + return TRUE; + } + else + { + return FALSE; + } } else { return FALSE; } } - else + } + else + { + len1 = strlen (*i1); + len2 = strlen (*i2); + + if (!utf8_caselessnmatch (*i1, *i2, len1, len2)) { - return FALSE; + if (*(i2 + 1) == NULL) /* if this is the last line */ + { + if (utf8_caselessnmatch (*i2, *i1, len2, len1)) + { + /* We matched ignoring the trailing stuff in vec2 */ + return TRUE; + } + else + { + return FALSE; + } + } + else + { + return FALSE; + } } } + ++i1; ++i2; } if (*i1 || *i2) - { - return FALSE; - } + return FALSE; else return TRUE; } @@ -4739,10 +5046,12 @@ struct _LinesWindow { gint n_lines; gchar **lines; + GtkTextIter first_line_start; GtkTextIter first_line_end; - gboolean slice; - gboolean visible_only; + + guint slice : 1; + guint visible_only : 1; }; static void @@ -4896,6 +5205,7 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, gboolean retval = FALSE; gboolean visible_only; gboolean slice; + gboolean case_insensitive; g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (str != NULL, FALSE); @@ -4926,18 +5236,11 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, visible_only = (flags & GTK_TEXT_SEARCH_VISIBLE_ONLY) != 0; slice = (flags & GTK_TEXT_SEARCH_TEXT_ONLY) == 0; - - /* locate all lines */ + case_insensitive = (flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE) != 0; - lines = strbreakup (str, "\n", -1); + /* locate all lines */ - l = lines; - n_lines = 0; - while (*l) - { - ++n_lines; - ++l; - } + lines = strbreakup (str, "\n", -1, &n_lines, case_insensitive); win.n_lines = n_lines; win.slice = slice; @@ -4950,7 +5253,7 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, do { - gchar *first_line_match; + const gchar *first_line_match; if (limit && gtk_text_iter_compare (limit, &win.first_line_end) > 0) @@ -4963,10 +5266,14 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, * end in '\n', so this will only match at the * end of the first line, which is correct. */ - first_line_match = g_strrstr (*win.lines, *lines); + if (!case_insensitive) + first_line_match = g_strrstr (*win.lines, *lines); + else + first_line_match = utf8_strrcasestr (*win.lines, *lines); if (first_line_match && - vectors_equal_ignoring_trailing (lines + 1, win.lines + 1)) + vectors_equal_ignoring_trailing (lines + 1, win.lines + 1, + case_insensitive)) { /* Match! */ gint offset; @@ -4979,7 +5286,7 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, next = win.first_line_start; start_tmp = next; forward_chars_with_skipping (&start_tmp, offset, - visible_only, !slice); + visible_only, !slice, FALSE); if (limit && gtk_text_iter_compare (limit, &start_tmp) > 0) @@ -4997,7 +5304,7 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, } forward_chars_with_skipping (&next, offset, - visible_only, !slice); + visible_only, !slice, TRUE); if (match_end) *match_end = next;