X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktextiter.c;h=2b41c34cfbb3b84eb03249a564bc8bc102f357b5;hb=HEAD;hp=b61f67b6374f3c1de500396ef393f121795dd973;hpb=d9e0f22ac7e95c20c5a15599d71fcd5b7cbd131c;p=~andy%2Fgtk diff --git a/gtk/gtktextiter.c b/gtk/gtktextiter.c index b61f67b63..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 . */ /* @@ -25,19 +23,32 @@ */ #define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API -#include -#include "gtkalias.h" +#include "config.h" #include "gtktextiter.h" #include "gtktextbtree.h" #include "gtktextiterprivate.h" +#include "gtkintl.h" #include "gtkdebug.h" + #include + +/** + * SECTION:gtktextiter + * @Short_description: Text buffer iterator + * @Title: GtkTextIter + * + * You may wish to begin by reading the text widget + * conceptual overview which gives an overview of all the objects and data + * types related to the text widget and how they work together. + */ + + #define FIX_OVERFLOWS(varname) if ((varname) == G_MININT) (varname) = G_MININT + 1 typedef struct _GtkTextRealIter GtkTextRealIter; -struct _GtkTextRealIter +struct G_GNUC_MAY_ALIAS _GtkTextRealIter { /* Always-valid information */ GtkTextBTree *tree; @@ -299,24 +310,12 @@ iter_init_from_char_offset (GtkTextIter *iter, return real; } -static inline void -invalidate_segment (GtkTextRealIter *iter) -{ - iter->segments_changed_stamp -= 1; -} - static inline void invalidate_char_index (GtkTextRealIter *iter) { iter->cached_char_index = -1; } -static inline void -invalidate_line_number (GtkTextRealIter *iter) -{ - iter->cached_line_number = -1; -} - static inline void adjust_char_index (GtkTextRealIter *iter, gint count) { @@ -331,28 +330,6 @@ adjust_line_number (GtkTextRealIter *iter, gint count) iter->cached_line_number += count; } -static inline void -adjust_char_offsets (GtkTextRealIter *iter, gint count) -{ - if (iter->line_char_offset >= 0) - { - iter->line_char_offset += count; - g_assert (iter->segment_char_offset >= 0); - iter->segment_char_offset += count; - } -} - -static inline void -adjust_byte_offsets (GtkTextRealIter *iter, gint count) -{ - if (iter->line_byte_offset >= 0) - { - iter->line_byte_offset += count; - g_assert (iter->segment_byte_offset >= 0); - iter->segment_byte_offset += count; - } -} - static inline void ensure_char_offsets (GtkTextRealIter *iter) { @@ -387,15 +364,15 @@ is_segment_start (GtkTextRealIter *real) return real->segment_byte_offset == 0 || real->segment_char_offset == 0; } -#if 1 +#ifdef G_ENABLE_DEBUG static void check_invariants (const GtkTextIter *iter) { - if (gtk_debug_flags & GTK_DEBUG_TEXT) + if (gtk_get_debug_flags () & GTK_DEBUG_TEXT) _gtk_text_iter_check (iter); } #else -#define check_invariants (x) +#define check_invariants(x) #endif /** @@ -404,7 +381,7 @@ check_invariants (const GtkTextIter *iter) * * Returns the #GtkTextBuffer this iterator is associated with. * - * Return value: the buffer + * Return value: (transfer none): the buffer **/ GtkTextBuffer* gtk_text_iter_get_buffer (const GtkTextIter *iter) @@ -441,7 +418,7 @@ gtk_text_iter_copy (const GtkTextIter *iter) g_return_val_if_fail (iter != NULL, NULL); - new_iter = g_new (GtkTextIter, 1); + new_iter = g_slice_new (GtkTextIter); *new_iter = *iter; @@ -462,22 +439,35 @@ gtk_text_iter_free (GtkTextIter *iter) { g_return_if_fail (iter != NULL); - g_free (iter); + g_slice_free (GtkTextIter, iter); } -GType -gtk_text_iter_get_type (void) +/** + * 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) { - static GType our_type = 0; - - if (our_type == 0) - our_type = g_boxed_type_register_static ("GtkTextIter", - (GBoxedCopyFunc) gtk_text_iter_copy, - (GBoxedFreeFunc) gtk_text_iter_free); + g_return_if_fail (iter != NULL); + g_return_if_fail (other != NULL); - return our_type; + *iter = *other; } +G_DEFINE_BOXED_TYPE (GtkTextIter, gtk_text_iter, + gtk_text_iter_copy, + gtk_text_iter_free) + GtkTextLineSegment* _gtk_text_iter_get_indexable_segment (const GtkTextIter *iter) { @@ -1005,7 +995,7 @@ gtk_text_iter_get_visible_text (const GtkTextIter *start, * (with no new reference count added). Otherwise, * %NULL is returned. * - * Return value: the pixbuf at @iter + * Return value: (transfer none): the pixbuf at @iter **/ GdkPixbuf* gtk_text_iter_get_pixbuf (const GtkTextIter *iter) @@ -1035,7 +1025,7 @@ gtk_text_iter_get_pixbuf (const GtkTextIter *iter) * anchor is returned (with no new reference count added). Otherwise, * %NULL is returned. * - * Return value: the anchor at @iter + * Return value: (transfer none): the anchor at @iter **/ GtkTextChildAnchor* gtk_text_iter_get_child_anchor (const GtkTextIter *iter) @@ -1067,7 +1057,7 @@ gtk_text_iter_get_child_anchor (const GtkTextIter *iter) * can exist in the same place. The returned list is not in any * meaningful order. * - * Return value: list of #GtkTextMark + * Return value: (element-type GtkTextMark) (transfer container): list of #GtkTextMark **/ GSList* gtk_text_iter_get_marks (const GtkTextIter *iter) @@ -1113,7 +1103,7 @@ gtk_text_iter_get_marks (const GtkTextIter *iter) * a tag is toggled off, then some non-empty range following @iter * does not have the tag applied to it. * - * Return value: tags toggled at this point + * Return value: (element-type GtkTextTag) (transfer container): tags toggled at this point **/ GSList* gtk_text_iter_get_toggled_tags (const GtkTextIter *iter, @@ -1162,7 +1152,7 @@ gtk_text_iter_get_toggled_tags (const GtkTextIter *iter, /** * gtk_text_iter_begins_tag: * @iter: an iterator - * @tag: a #GtkTextTag, or %NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * * Returns %TRUE if @tag is toggled on at exactly this point. If @tag * is %NULL, returns %TRUE if any tag is toggled on at this point. Note @@ -1208,7 +1198,7 @@ gtk_text_iter_begins_tag (const GtkTextIter *iter, /** * gtk_text_iter_ends_tag: * @iter: an iterator - * @tag: a #GtkTextTag, or %NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * * Returns %TRUE if @tag is toggled off at exactly this point. If @tag * is %NULL, returns %TRUE if any tag is toggled off at this point. Note @@ -1255,7 +1245,7 @@ gtk_text_iter_ends_tag (const GtkTextIter *iter, /** * gtk_text_iter_toggles_tag: * @iter: an iterator - * @tag: a #GtkTextTag, or %NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * * This is equivalent to (gtk_text_iter_begins_tag () || * gtk_text_iter_ends_tag ()), i.e. it tells you whether a range with @@ -1340,8 +1330,8 @@ gtk_text_iter_has_tag (const GtkTextIter *iter, * priority (highest-priority tags are last). The #GtkTextTag in the * list don't have a reference added, but you have to free the list * itself. - * - * Return value: list of #GtkTextTag + * + * Return value: (element-type GtkTextTag) (transfer container): list of #GtkTextTag **/ GSList* gtk_text_iter_get_tags (const GtkTextIter *iter) @@ -1359,15 +1349,11 @@ gtk_text_iter_get_tags (const GtkTextIter *iter) /* No tags, use default style */ if (tags == NULL || tag_count == 0) { - if (tags) - g_free (tags); + g_free (tags); return NULL; } - /* Sort tags in ascending order of priority */ - _gtk_text_tag_array_sort (tags, tag_count); - retval = NULL; i = 0; while (i < tag_count) @@ -1547,13 +1533,10 @@ gtk_text_iter_starts_line (const GtkTextIter *iter) gboolean gtk_text_iter_ends_line (const GtkTextIter *iter) { - GtkTextRealIter *real; gunichar wc; g_return_val_if_fail (iter != NULL, FALSE); - real = gtk_text_iter_make_real (iter); - check_invariants (iter); /* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in @@ -1567,10 +1550,23 @@ gtk_text_iter_ends_line (const GtkTextIter *iter) return TRUE; else if (wc == '\n') { + GtkTextIter tmp = *iter; + /* need to determine if a \r precedes the \n, in which case - * we aren't the end of the line + * we aren't the end of the line. + * Note however that if \r and \n are on different lines, they + * both are terminators. This for instance may happen after + * deleting some text: + + 1 some text\r delete 'a' 1 some text\r + 2 a\n ---------> 2 \n + 3 ... 3 ... + */ - GtkTextIter tmp = *iter; + + if (gtk_text_iter_get_line_offset (&tmp) == 0) + return TRUE; + if (!gtk_text_iter_backward_char (&tmp)) return TRUE; @@ -1741,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 @@ -1767,15 +1763,11 @@ gtk_text_iter_get_attributes (const GtkTextIter *iter, /* No tags, use default style */ if (tags == NULL || tag_count == 0) { - if (tags) - g_free (tags); + g_free (tags); return FALSE; } - /* Sort tags in ascending order of priority */ - _gtk_text_tag_array_sort (tags, tag_count); - _gtk_text_attributes_fill_from_tags (values, tags, tag_count); @@ -2389,28 +2381,25 @@ gtk_text_iter_backward_chars (GtkTextIter *iter, gint count) g_assert (real->segment->char_count > 0); g_assert (real->segment->type == >k_text_char_type); - real->segment_char_offset -= count; - g_assert (real->segment_char_offset >= 0); - if (real->line_byte_offset >= 0) { + const char *p; gint new_byte_offset; - gint i; - new_byte_offset = 0; - i = 0; - while (i < real->segment_char_offset) - { - const char * start = real->segment->body.chars + new_byte_offset; - new_byte_offset += g_utf8_next_char (start) - start; - - ++i; - } + /* if in the last fourth of the segment walk backwards */ + if (count < real->segment_char_offset / 4) + p = g_utf8_offset_to_pointer (real->segment->body.chars + real->segment_byte_offset, + -count); + else + p = g_utf8_offset_to_pointer (real->segment->body.chars, + real->segment_char_offset - count); + new_byte_offset = p - real->segment->body.chars; real->line_byte_offset -= (real->segment_byte_offset - new_byte_offset); real->segment_byte_offset = new_byte_offset; } + real->segment_char_offset -= count; real->line_char_offset -= count; adjust_char_index (real, 0 - count); @@ -2483,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 * @@ -2507,10 +2496,10 @@ gtk_text_iter_backward_text_chars (GtkTextIter *iter, * gtk_text_iter_forward_line: * @iter: an iterator * - * Moves @iter to the start of the next line. Returns %TRUE if there - * was a next line to move to, and %FALSE if @iter was simply moved to - * the end of the buffer and is now not dereferenceable, or if @iter was - * already at the end of the buffer. + * Moves @iter to the start of the next line. If the iter is already on the + * last line of the buffer, moves the iter to the end of the current line. + * If after the operation, the iter is at the end of the buffer and not + * dereferencable, returns %FALSE. Otherwise, returns %TRUE. * * Return value: whether @iter can be dereferenced **/ @@ -2719,6 +2708,162 @@ gtk_text_iter_backward_lines (GtkTextIter *iter, gint count) } } +/** + * gtk_text_iter_forward_visible_line: + * @iter: an iterator + * + * Moves @iter to the start of the next visible line. Returns %TRUE if there + * was a next line to move to, and %FALSE if @iter was simply moved to + * the end of the buffer and is now not dereferenceable, or if @iter was + * already at the end of the buffer. + * + * Return value: whether @iter can be dereferenced + * + * Since: 2.8 + **/ +gboolean +gtk_text_iter_forward_visible_line (GtkTextIter *iter) +{ + while (gtk_text_iter_forward_line (iter)) + { + if (!_gtk_text_btree_char_is_invisible (iter)) + return TRUE; + else + { + do + { + if (!gtk_text_iter_forward_char (iter)) + return FALSE; + + if (!_gtk_text_btree_char_is_invisible (iter)) + return TRUE; + } + while (!gtk_text_iter_ends_line (iter)); + } + } + + return FALSE; +} + +/** + * gtk_text_iter_backward_visible_line: + * @iter: an iterator + * + * Moves @iter to the start of the previous visible line. Returns %TRUE if + * @iter could be moved; i.e. if @iter was at character offset 0, this + * function returns %FALSE. Therefore if @iter was already on line 0, + * but not at the start of the line, @iter is snapped to the start of + * the line and the function returns %TRUE. (Note that this implies that + * in a loop calling this function, the line number may not change on + * every iteration, if your first iteration is on line 0.) + * + * Return value: whether @iter moved + * + * Since: 2.8 + **/ +gboolean +gtk_text_iter_backward_visible_line (GtkTextIter *iter) +{ + while (gtk_text_iter_backward_line (iter)) + { + if (!_gtk_text_btree_char_is_invisible (iter)) + return TRUE; + else + { + do + { + if (!gtk_text_iter_backward_char (iter)) + return FALSE; + + if (!_gtk_text_btree_char_is_invisible (iter)) + return TRUE; + } + while (!gtk_text_iter_starts_line (iter)); + } + } + + return FALSE; +} + +/** + * gtk_text_iter_forward_visible_lines: + * @iter: a #GtkTextIter + * @count: number of lines to move forward + * + * Moves @count visible lines forward, if possible (if @count would move + * past the start or end of the buffer, moves to the start or end of + * the buffer). The return value indicates whether the iterator moved + * onto a dereferenceable position; if the iterator didn't move, or + * moved onto the end iterator, then %FALSE is returned. If @count is 0, + * the function does nothing and returns %FALSE. If @count is negative, + * moves backward by 0 - @count lines. + * + * Return value: whether @iter moved and is dereferenceable + * + * Since: 2.8 + **/ +gboolean +gtk_text_iter_forward_visible_lines (GtkTextIter *iter, + gint count) +{ + FIX_OVERFLOWS (count); + + if (count < 0) + return gtk_text_iter_backward_visible_lines (iter, 0 - count); + else if (count == 0) + return FALSE; + else if (count == 1) + { + check_invariants (iter); + return gtk_text_iter_forward_visible_line (iter); + } + else + { + while (gtk_text_iter_forward_visible_line (iter) && count > 0) + count--; + return count == 0; + } +} + +/** + * gtk_text_iter_backward_visible_lines: + * @iter: a #GtkTextIter + * @count: number of lines to move backward + * + * Moves @count visible lines backward, if possible (if @count would move + * past the start or end of the buffer, moves to the start or end of + * the buffer). The return value indicates whether the iterator moved + * onto a dereferenceable position; if the iterator didn't move, or + * moved onto the end iterator, then %FALSE is returned. If @count is 0, + * the function does nothing and returns %FALSE. If @count is negative, + * moves forward by 0 - @count lines. + * + * Return value: whether @iter moved and is dereferenceable + * + * Since: 2.8 + **/ +gboolean +gtk_text_iter_backward_visible_lines (GtkTextIter *iter, + gint count) +{ + FIX_OVERFLOWS (count); + + if (count < 0) + return gtk_text_iter_forward_visible_lines (iter, 0 - count); + else if (count == 0) + return FALSE; + else if (count == 1) + { + return gtk_text_iter_backward_visible_line (iter); + } + else + { + while (gtk_text_iter_backward_visible_line (iter) && count > 0) + count--; + return count == 0; + } +} + typedef gboolean (* FindLogAttrFunc) (const PangoLogAttr *attrs, gint offset, gint min_offset, @@ -3117,7 +3262,7 @@ gtk_text_iter_forward_word_ends (GtkTextIter *iter, } /** - * gtk_text_iter_backward_word_starts + * gtk_text_iter_backward_word_starts: * @iter: a #GtkTextIter * @count: number of times to move * @@ -3195,7 +3340,7 @@ gtk_text_iter_forward_visible_word_ends (GtkTextIter *iter, } /** - * gtk_text_iter_backward_visible_word_starts + * gtk_text_iter_backward_visible_word_starts: * @iter: a #GtkTextIter * @count: number of times to move * @@ -3581,7 +3726,7 @@ gtk_text_iter_forward_visible_cursor_positions (GtkTextIter *iter, * @count: number of positions to move * * Moves up to @count visible cursor positions. See - * gtk_text_iter_forward_cursor_position() for details. + * gtk_text_iter_backward_cursor_position() for details. * * Return value: %TRUE if we moved and the new position is dereferenceable * @@ -3740,12 +3885,6 @@ gtk_text_iter_set_visible_line_offset (GtkTextIter *iter, gtk_text_iter_forward_line (iter); } -static gint -bytes_in_char (GtkTextIter *iter) -{ - return g_unichar_to_utf8 (gtk_text_iter_get_char (iter), NULL); -} - /** * gtk_text_iter_set_visible_line_index: * @iter: a #GtkTextIter @@ -3756,40 +3895,51 @@ bytes_in_char (GtkTextIter *iter) * in the index. **/ void -gtk_text_iter_set_visible_line_index (GtkTextIter *iter, - gint byte_on_line) +gtk_text_iter_set_visible_line_index (GtkTextIter *iter, + gint byte_on_line) { - gint bytes_seen = 0; - gint skipped = 0; + GtkTextRealIter *real; + gint offset = 0; GtkTextIter pos; - - g_return_if_fail (iter != NULL); + GtkTextLineSegment *seg; + g_return_if_fail (iter != NULL); + gtk_text_iter_set_line_offset (iter, 0); pos = *iter; - /* For now we use a ludicrously slow implementation */ - while (bytes_seen < byte_on_line) + real = gtk_text_iter_make_real (&pos); + + if (real == NULL) + return; + + ensure_byte_offsets (real); + + check_invariants (&pos); + + seg = _gtk_text_iter_get_indexable_segment (&pos); + + while (seg != NULL && byte_on_line > 0) { if (!_gtk_text_btree_char_is_invisible (&pos)) - bytes_seen += bytes_in_char (&pos); - else skipped++; - - if (!gtk_text_iter_forward_char (&pos)) - break; + { + if (byte_on_line < seg->byte_count) + { + iter_set_from_byte_offset (real, real->line, offset + byte_on_line); + byte_on_line = 0; + break; + } + else + byte_on_line -= seg->byte_count; + } - if (bytes_seen >= byte_on_line) - break; + offset += seg->byte_count; + _gtk_text_iter_forward_indexable_segment (&pos); + seg = _gtk_text_iter_get_indexable_segment (&pos); } - if (bytes_seen > byte_on_line) - g_warning ("%s: Incorrect visible byte index %d falls in the middle of a UTF-8 " - "character; this will crash the text buffer. " - "Byte indexes must refer to the start of a character.", - G_STRLOC, byte_on_line); - - if (_gtk_text_iter_get_text_line (&pos) == _gtk_text_iter_get_text_line (iter)) + if (byte_on_line == 0) *iter = pos; else gtk_text_iter_forward_line (iter); @@ -3984,7 +4134,7 @@ gtk_text_iter_forward_to_line_end (GtkTextIter *iter) /** * gtk_text_iter_forward_to_tag_toggle: * @iter: a #GtkTextIter - * @tag: a #GtkTextTag, or %NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * * Moves forward to the next toggle (on or off) of the * #GtkTextTag @tag, or to the next toggle of any tag if @@ -4066,7 +4216,7 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter, /** * gtk_text_iter_backward_to_tag_toggle: * @iter: a #GtkTextIter - * @tag: a #GtkTextTag, or %NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * * Moves backward to the next toggle (on or off) of the * #GtkTextTag @tag, or to the next toggle of any tag if @@ -4179,9 +4329,9 @@ matches_pred (GtkTextIter *iter, /** * gtk_text_iter_forward_find_char: * @iter: a #GtkTextIter - * @pred: a function to be called on each character + * @pred: (scope call): a function to be called on each character * @user_data: user data for @pred - * @limit: search limit, or %NULL for none + * @limit: (allow-none): search limit, or %NULL for none * * Advances @iter, calling @pred on each character. If * @pred returns %TRUE, returns %TRUE and stops scanning. @@ -4217,9 +4367,9 @@ gtk_text_iter_forward_find_char (GtkTextIter *iter, /** * gtk_text_iter_backward_find_char: * @iter: a #GtkTextIter - * @pred: function to be called on each character + * @pred: (scope call): function to be called on each character * @user_data: user data for @pred - * @limit: search limit, or %NULL for none + * @limit: (allow-none): search limit, or %NULL for none * * Same as gtk_text_iter_forward_find_char(), but goes backward from @iter. * @@ -4253,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; @@ -4266,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; @@ -4275,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) @@ -4282,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) { @@ -4330,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; } @@ -4356,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); @@ -4380,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); @@ -4407,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; @@ -4423,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); @@ -4436,6 +4835,9 @@ strbreakup (const char *string, g_slist_free (string_list); + if (num_strings != NULL) + *num_strings = n - 1; + return str_array; } @@ -4444,12 +4846,12 @@ strbreakup (const char *string, * @iter: start of search * @str: a search string * @flags: flags affecting how the search is done - * @match_start: return location for start of match, or %NULL - * @match_end: return location for end of match, or %NULL - * @limit: bound for the search, or %NULL for the end of the buffer - * - * Searches forward for @str. Any match is returned by setting - * @match_start to the first character of the match and @match_end to the + * @match_start: (out caller-allocates) (allow-none): return location for start of match, or %NULL + * @match_end: (out caller-allocates) (allow-none): return location for end of match, or %NULL + * @limit: (allow-none): bound for the search, or %NULL for the end of the buffer + * + * Searches forward for @str. Any match is returned by setting + * @match_start to the first character of the match and @match_end to the * first character after the match. The search will not continue past * @limit. Note that a search is a linear or O(n) operation, so you * may wish to use @limit to avoid locking up your UI on large @@ -4462,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 **/ @@ -4479,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); @@ -4510,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; @@ -4530,11 +4936,11 @@ 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 && - gtk_text_iter_compare (&end, limit) < 0)) + gtk_text_iter_compare (&end, limit) <= 0)) { retval = TRUE; @@ -4556,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 */ @@ -4568,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; } @@ -4609,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 @@ -4673,6 +5112,8 @@ lines_window_init (LinesWindow *win, } win->lines[i] = line_text; + win->first_line_start = line_start; + win->first_line_end = line_end; line_end = line_start; gtk_text_iter_backward_line (&line_start); @@ -4741,12 +5182,12 @@ lines_window_free (LinesWindow *win) * @iter: a #GtkTextIter where the search begins * @str: search string * @flags: bitmask of flags affecting the search - * @match_start: return location for start of match, or %NULL - * @match_end: return location for end of match, or %NULL - * @limit: location of last possible @match_start, or %NULL for start of buffer - * + * @match_start: (out caller-allocates) (allow-none): return location for start of match, or %NULL + * @match_end: (out caller-allocates) (allow-none): return location for end of match, or %NULL + * @limit: (allow-none): location of last possible @match_start, or %NULL for start of buffer + * * Same as gtk_text_iter_forward_search(), but moves backward. - * + * * Return value: whether a match was found **/ gboolean @@ -4764,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); @@ -4794,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; @@ -4818,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) @@ -4831,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; @@ -4847,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) @@ -4865,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; @@ -5445,4 +5884,3 @@ _gtk_text_iter_check (const GtkTextIter *iter) if (_gtk_text_line_is_last (real->line, real->tree)) g_error ("Iterator was on last line (past the end iterator)"); } -