X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktextiter.c;h=2b41c34cfbb3b84eb03249a564bc8bc102f357b5;hb=fa8b71450c764bb0d4d3514c8372637ea3c64c35;hp=b7ab25fa1038a4307f87c4270dc8db0eafbc3d58;hpb=a9fbfc99f522be13c5963a5bd102ccbad07a5bd5;p=~andy%2Fgtk
diff --git a/gtk/gtktextiter.c b/gtk/gtktextiter.c
index b7ab25fa1..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,17 +23,32 @@
*/
#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
+#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;
@@ -297,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)
{
@@ -329,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)
{
@@ -385,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
/**
@@ -402,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)
@@ -439,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;
@@ -460,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)
{
@@ -1003,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)
@@ -1033,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)
@@ -1065,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)
@@ -1111,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,
@@ -1160,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
@@ -1206,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
@@ -1253,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
@@ -1338,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)
@@ -1357,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)
@@ -1545,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
@@ -1565,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;
@@ -1739,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
@@ -1765,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);
@@ -2387,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);
@@ -2481,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
*
@@ -2505,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
**/
@@ -2717,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,
@@ -2802,7 +2949,10 @@ inside_word_func (const PangoLogAttr *attrs,
!(attrs[offset].is_word_start || attrs[offset].is_word_end))
--offset;
- return attrs[offset].is_word_start;
+ if (offset >= 0)
+ return attrs[offset].is_word_start;
+ else
+ return FALSE;
}
/* Sentence funcs */
@@ -3112,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
*
@@ -3190,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
*
@@ -3576,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
*
@@ -3712,6 +3862,8 @@ gtk_text_iter_set_visible_line_offset (GtkTextIter *iter,
g_return_if_fail (iter != NULL);
+ gtk_text_iter_set_line_offset (iter, 0);
+
pos = *iter;
/* For now we use a ludicrously slow implementation */
@@ -3733,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
@@ -3749,38 +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);
@@ -3975,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
@@ -4057,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
@@ -4170,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.
@@ -4208,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.
*
@@ -4244,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;
@@ -4257,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;
@@ -4266,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)
@@ -4273,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)
{
@@ -4321,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;
}
@@ -4347,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);
@@ -4371,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);
@@ -4398,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;
@@ -4414,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);
@@ -4427,6 +4835,9 @@ strbreakup (const char *string,
g_slist_free (string_list);
+ if (num_strings != NULL)
+ *num_strings = n - 1;
+
return str_array;
}
@@ -4435,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
@@ -4453,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
**/
@@ -4470,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);
@@ -4501,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;
@@ -4521,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;
@@ -4547,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 */
@@ -4559,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;
}
@@ -4600,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
@@ -4664,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);
@@ -4732,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
@@ -4755,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);
@@ -4785,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;
@@ -4809,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)
@@ -4822,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;
@@ -4838,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)
@@ -4856,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;
@@ -5436,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)");
}
-