| /* GTK - The GIMP Toolkit |
| * gtktextiter.c Copyright (C) 2000 Red Hat, Inc. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * 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, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /* |
| * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
| * file for a list of people on the GTK+ Team. See the ChangeLog |
| * files for a list of changes. These files are distributed with |
| * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
| */ |
| |
| #include "config.h" |
| #include "gtktextiter.h" |
| #include "gtktextbtree.h" |
| #include "gtktextbufferprivate.h" |
| #include "gtktextiterprivate.h" |
| #include "gtkintl.h" |
| #include "gtkdebug.h" |
| |
| #include <string.h> |
| |
| |
| /** |
| * SECTION:gtktextiter |
| * @Short_description: Text buffer iterator |
| * @Title: GtkTextIter |
| * |
| * You may wish to begin by reading the |
| * [text widget conceptual overview][TextWidget] |
| * 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 G_GNUC_MAY_ALIAS _GtkTextRealIter |
| { |
| /* Always-valid information */ |
| GtkTextBTree *tree; |
| GtkTextLine *line; |
| /* At least one of these is always valid; |
| if invalid, they are -1. |
| |
| If the line byte offset is valid, so is the segment byte offset; |
| and ditto for char offsets. */ |
| gint line_byte_offset; |
| gint line_char_offset; |
| /* These two are valid if >= 0 */ |
| gint cached_char_index; |
| gint cached_line_number; |
| /* Stamps to detect the buffer changing under us */ |
| gint chars_changed_stamp; |
| gint segments_changed_stamp; |
| /* Valid if the segments_changed_stamp is up-to-date */ |
| GtkTextLineSegment *segment; /* indexable segment we index */ |
| GtkTextLineSegment *any_segment; /* first segment in our location, |
| maybe same as "segment" */ |
| /* One of these will always be valid if segments_changed_stamp is |
| up-to-date. If invalid, they are -1. |
| |
| If the line byte offset is valid, so is the segment byte offset; |
| and ditto for char offsets. */ |
| gint segment_byte_offset; |
| gint segment_char_offset; |
| |
| /* padding */ |
| gint pad1; |
| gpointer pad2; |
| }; |
| |
| /* These "set" functions should not assume any fields |
| other than the char stamp and the tree are valid. |
| */ |
| static void |
| iter_set_common (GtkTextRealIter *iter, |
| GtkTextLine *line) |
| { |
| /* Update segments stamp */ |
| iter->segments_changed_stamp = |
| _gtk_text_btree_get_segments_changed_stamp (iter->tree); |
| |
| iter->line = line; |
| |
| iter->line_byte_offset = -1; |
| iter->line_char_offset = -1; |
| iter->segment_byte_offset = -1; |
| iter->segment_char_offset = -1; |
| iter->cached_char_index = -1; |
| iter->cached_line_number = -1; |
| } |
| |
| static void |
| iter_set_from_byte_offset (GtkTextRealIter *iter, |
| GtkTextLine *line, |
| gint byte_offset) |
| { |
| iter_set_common (iter, line); |
| |
| if (!_gtk_text_line_byte_locate (iter->line, |
| byte_offset, |
| &iter->segment, |
| &iter->any_segment, |
| &iter->segment_byte_offset, |
| &iter->line_byte_offset)) |
| g_error ("Byte index %d is off the end of the line", |
| byte_offset); |
| } |
| |
| static void |
| iter_set_from_char_offset (GtkTextRealIter *iter, |
| GtkTextLine *line, |
| gint char_offset) |
| { |
| iter_set_common (iter, line); |
| |
| if (!_gtk_text_line_char_locate (iter->line, |
| char_offset, |
| &iter->segment, |
| &iter->any_segment, |
| &iter->segment_char_offset, |
| &iter->line_char_offset)) |
| g_error ("Char offset %d is off the end of the line", |
| char_offset); |
| } |
| |
| static void |
| iter_set_from_segment (GtkTextRealIter *iter, |
| GtkTextLine *line, |
| GtkTextLineSegment *segment) |
| { |
| GtkTextLineSegment *seg; |
| gint byte_offset; |
| |
| /* This could theoretically be optimized by computing all the iter |
| fields in this same loop, but I'm skipping it for now. */ |
| byte_offset = 0; |
| seg = line->segments; |
| while (seg != segment) |
| { |
| byte_offset += seg->byte_count; |
| seg = seg->next; |
| } |
| |
| iter_set_from_byte_offset (iter, line, byte_offset); |
| } |
| |
| /* This function ensures that the segment-dependent information is |
| truly computed lazily; often we don't need to do the full make_real |
| work. This ensures the btree and line are valid, but doesn't |
| update the segments. */ |
| static GtkTextRealIter* |
| gtk_text_iter_make_surreal (const GtkTextIter *_iter) |
| { |
| GtkTextRealIter *iter = (GtkTextRealIter*)_iter; |
| |
| if (iter->chars_changed_stamp != |
| _gtk_text_btree_get_chars_changed_stamp (iter->tree)) |
| { |
| g_warning ("Invalid text buffer iterator: either the iterator " |
| "is uninitialized, or the characters/textures/widgets " |
| "in the buffer have been modified since the iterator " |
| "was created.\nYou must use marks, character numbers, " |
| "or line numbers to preserve a position across buffer " |
| "modifications.\nYou can apply tags and insert marks " |
| "without invalidating your iterators,\n" |
| "but any mutation that affects 'indexable' buffer contents " |
| "(contents that can be referred to by character offset)\n" |
| "will invalidate all outstanding iterators"); |
| return NULL; |
| } |
| |
| /* We don't update the segments information since we are becoming |
| only surreal. However we do invalidate the segments information |
| if appropriate, to be sure we segfault if we try to use it and we |
| should have used make_real. */ |
| |
| if (iter->segments_changed_stamp != |
| _gtk_text_btree_get_segments_changed_stamp (iter->tree)) |
| { |
| iter->segment = NULL; |
| iter->any_segment = NULL; |
| /* set to segfault-causing values. */ |
| iter->segment_byte_offset = -10000; |
| iter->segment_char_offset = -10000; |
| } |
| |
| return iter; |
| } |
| |
| static GtkTextRealIter* |
| gtk_text_iter_make_real (const GtkTextIter *_iter) |
| { |
| GtkTextRealIter *iter; |
| |
| iter = gtk_text_iter_make_surreal (_iter); |
| |
| if (iter->segments_changed_stamp != |
| _gtk_text_btree_get_segments_changed_stamp (iter->tree)) |
| { |
| if (iter->line_byte_offset >= 0) |
| { |
| iter_set_from_byte_offset (iter, |
| iter->line, |
| iter->line_byte_offset); |
| } |
| else |
| { |
| g_assert (iter->line_char_offset >= 0); |
| |
| iter_set_from_char_offset (iter, |
| iter->line, |
| iter->line_char_offset); |
| } |
| } |
| |
| g_assert (iter->segment != NULL); |
| g_assert (iter->any_segment != NULL); |
| g_assert (iter->segment->char_count > 0); |
| |
| return iter; |
| } |
| |
| static GtkTextRealIter* |
| iter_init_common (GtkTextIter *_iter, |
| GtkTextBTree *tree) |
| { |
| GtkTextRealIter *iter = (GtkTextRealIter*)_iter; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| g_return_val_if_fail (tree != NULL, NULL); |
| |
| memset (iter, 0, sizeof (GtkTextRealIter)); |
| |
| iter->tree = tree; |
| |
| iter->chars_changed_stamp = |
| _gtk_text_btree_get_chars_changed_stamp (iter->tree); |
| |
| return iter; |
| } |
| |
| static GtkTextRealIter* |
| iter_init_from_segment (GtkTextIter *iter, |
| GtkTextBTree *tree, |
| GtkTextLine *line, |
| GtkTextLineSegment *segment) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (line != NULL, NULL); |
| |
| real = iter_init_common (iter, tree); |
| |
| iter_set_from_segment (real, line, segment); |
| |
| return real; |
| } |
| |
| static GtkTextRealIter* |
| iter_init_from_byte_offset (GtkTextIter *iter, |
| GtkTextBTree *tree, |
| GtkTextLine *line, |
| gint line_byte_offset) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (line != NULL, NULL); |
| |
| real = iter_init_common (iter, tree); |
| |
| iter_set_from_byte_offset (real, line, line_byte_offset); |
| |
| if (real->segment->type == >k_text_char_type && |
| (real->segment->body.chars[real->segment_byte_offset] & 0xc0) == 0x80) |
| g_warning ("Incorrect line 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.", |
| line_byte_offset); |
| |
| return real; |
| } |
| |
| static GtkTextRealIter* |
| iter_init_from_char_offset (GtkTextIter *iter, |
| GtkTextBTree *tree, |
| GtkTextLine *line, |
| gint line_char_offset) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (line != NULL, NULL); |
| |
| real = iter_init_common (iter, tree); |
| |
| iter_set_from_char_offset (real, line, line_char_offset); |
| |
| return real; |
| } |
| |
| static inline void |
| invalidate_char_index (GtkTextRealIter *iter) |
| { |
| iter->cached_char_index = -1; |
| } |
| |
| static inline void |
| adjust_char_index (GtkTextRealIter *iter, gint count) |
| { |
| if (iter->cached_char_index >= 0) |
| iter->cached_char_index += count; |
| } |
| |
| static inline void |
| adjust_line_number (GtkTextRealIter *iter, gint count) |
| { |
| if (iter->cached_line_number >= 0) |
| iter->cached_line_number += count; |
| } |
| |
| static inline void |
| ensure_char_offsets (GtkTextRealIter *iter) |
| { |
| if (iter->line_char_offset < 0) |
| { |
| g_assert (iter->line_byte_offset >= 0); |
| |
| _gtk_text_line_byte_to_char_offsets (iter->line, |
| iter->line_byte_offset, |
| &iter->line_char_offset, |
| &iter->segment_char_offset); |
| } |
| } |
| |
| static inline void |
| ensure_byte_offsets (GtkTextRealIter *iter) |
| { |
| if (iter->line_byte_offset < 0) |
| { |
| g_assert (iter->line_char_offset >= 0); |
| |
| _gtk_text_line_char_to_byte_offsets (iter->line, |
| iter->line_char_offset, |
| &iter->line_byte_offset, |
| &iter->segment_byte_offset); |
| } |
| } |
| |
| static inline gboolean |
| is_segment_start (GtkTextRealIter *real) |
| { |
| return real->segment_byte_offset == 0 || real->segment_char_offset == 0; |
| } |
| |
| #ifdef G_ENABLE_DEBUG |
| static void |
| check_invariants (const GtkTextIter *iter) |
| { |
| if (GTK_DEBUG_CHECK (TEXT)) |
| _gtk_text_iter_check (iter); |
| } |
| #else |
| #define check_invariants(x) |
| #endif |
| |
| /** |
| * gtk_text_iter_get_buffer: |
| * @iter: an iterator |
| * |
| * Returns the #GtkTextBuffer this iterator is associated with. |
| * |
| * Returns: (transfer none): the buffer |
| **/ |
| GtkTextBuffer* |
| gtk_text_iter_get_buffer (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return NULL; |
| |
| check_invariants (iter); |
| |
| return _gtk_text_btree_get_buffer (real->tree); |
| } |
| |
| /** |
| * gtk_text_iter_copy: |
| * @iter: an iterator |
| * |
| * Creates a dynamically-allocated copy of an iterator. This function |
| * is not useful in applications, because iterators can be copied with a |
| * simple assignment (`GtkTextIter i = j;`). The |
| * function is used by language bindings. |
| * |
| * Returns: a copy of the @iter, free with gtk_text_iter_free() |
| **/ |
| GtkTextIter* |
| gtk_text_iter_copy (const GtkTextIter *iter) |
| { |
| GtkTextIter *new_iter; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| new_iter = g_slice_new (GtkTextIter); |
| |
| *new_iter = *iter; |
| |
| return new_iter; |
| } |
| |
| /** |
| * gtk_text_iter_free: |
| * @iter: a dynamically-allocated iterator |
| * |
| * Free an iterator allocated on the heap. This function |
| * is intended for use in language bindings, and is not |
| * especially useful for applications, because iterators can |
| * simply be allocated on the stack. |
| **/ |
| void |
| gtk_text_iter_free (GtkTextIter *iter) |
| { |
| g_return_if_fail (iter != NULL); |
| |
| 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. |
| **/ |
| 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) |
| |
| GtkTextLineSegment* |
| _gtk_text_iter_get_indexable_segment (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return NULL; |
| |
| check_invariants (iter); |
| |
| g_assert (real->segment != NULL); |
| |
| return real->segment; |
| } |
| |
| GtkTextLineSegment* |
| _gtk_text_iter_get_any_segment (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return NULL; |
| |
| check_invariants (iter); |
| |
| g_assert (real->any_segment != NULL); |
| |
| return real->any_segment; |
| } |
| |
| gint |
| _gtk_text_iter_get_segment_byte (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| ensure_byte_offsets (real); |
| |
| check_invariants (iter); |
| |
| return real->segment_byte_offset; |
| } |
| |
| gint |
| _gtk_text_iter_get_segment_char (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| ensure_char_offsets (real); |
| |
| check_invariants (iter); |
| |
| return real->segment_char_offset; |
| } |
| |
| /* This function does not require a still-valid |
| iterator */ |
| GtkTextLine* |
| _gtk_text_iter_get_text_line (const GtkTextIter *iter) |
| { |
| const GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = (const GtkTextRealIter*)iter; |
| |
| return real->line; |
| } |
| |
| /* This function does not require a still-valid |
| iterator */ |
| GtkTextBTree* |
| _gtk_text_iter_get_btree (const GtkTextIter *iter) |
| { |
| const GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = (const GtkTextRealIter*)iter; |
| |
| return real->tree; |
| } |
| |
| /* |
| * Conversions |
| */ |
| |
| /** |
| * gtk_text_iter_get_offset: |
| * @iter: an iterator |
| * |
| * Returns the character offset of an iterator. |
| * Each character in a #GtkTextBuffer has an offset, |
| * starting with 0 for the first character in the buffer. |
| * Use gtk_text_buffer_get_iter_at_offset() to convert an |
| * offset back into an iterator. |
| * |
| * Returns: a character offset |
| **/ |
| gint |
| gtk_text_iter_get_offset (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| check_invariants (iter); |
| |
| if (real->cached_char_index < 0) |
| { |
| ensure_char_offsets (real); |
| |
| real->cached_char_index = |
| _gtk_text_line_char_index (real->line); |
| real->cached_char_index += real->line_char_offset; |
| } |
| |
| check_invariants (iter); |
| |
| return real->cached_char_index; |
| } |
| |
| /** |
| * gtk_text_iter_get_line: |
| * @iter: an iterator |
| * |
| * Returns the line number containing the iterator. Lines in |
| * a #GtkTextBuffer are numbered beginning with 0 for the first |
| * line in the buffer. |
| * |
| * Returns: a line number |
| **/ |
| gint |
| gtk_text_iter_get_line (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| if (real->cached_line_number < 0) |
| real->cached_line_number = |
| _gtk_text_line_get_number (real->line); |
| |
| check_invariants (iter); |
| |
| return real->cached_line_number; |
| } |
| |
| /** |
| * gtk_text_iter_get_line_offset: |
| * @iter: an iterator |
| * |
| * Returns the character offset of the iterator, |
| * counting from the start of a newline-terminated line. |
| * The first character on the line has offset 0. |
| * |
| * Returns: offset from start of line |
| **/ |
| gint |
| gtk_text_iter_get_line_offset (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| ensure_char_offsets (real); |
| |
| check_invariants (iter); |
| |
| return real->line_char_offset; |
| } |
| |
| /** |
| * gtk_text_iter_get_line_index: |
| * @iter: an iterator |
| * |
| * Returns the byte index of the iterator, counting |
| * from the start of a newline-terminated line. |
| * Remember that #GtkTextBuffer encodes text in |
| * UTF-8, and that characters can require a variable |
| * number of bytes to represent. |
| * |
| * Returns: distance from start of line, in bytes |
| **/ |
| gint |
| gtk_text_iter_get_line_index (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| ensure_byte_offsets (real); |
| |
| check_invariants (iter); |
| |
| return real->line_byte_offset; |
| } |
| |
| /** |
| * gtk_text_iter_get_visible_line_offset: |
| * @iter: a #GtkTextIter |
| * |
| * Returns the offset in characters from the start of the |
| * line to the given @iter, not counting characters that |
| * are invisible due to tags with the “invisible” flag |
| * toggled on. |
| * |
| * Returns: offset in visible characters from the start of the line |
| **/ |
| gint |
| gtk_text_iter_get_visible_line_offset (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| gint vis_offset; |
| GtkTextLineSegment *seg; |
| GtkTextIter pos; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| ensure_char_offsets (real); |
| |
| check_invariants (iter); |
| |
| vis_offset = real->line_char_offset; |
| |
| g_assert (vis_offset >= 0); |
| |
| _gtk_text_btree_get_iter_at_line (real->tree, |
| &pos, |
| real->line, |
| 0); |
| |
| seg = _gtk_text_iter_get_indexable_segment (&pos); |
| |
| while (seg != real->segment) |
| { |
| /* This is a pretty expensive call, making the |
| * whole function pretty lame; we could keep track |
| * of current invisibility state by looking at toggle |
| * segments as we loop, and then call this function |
| * only once per line, in order to speed up the loop |
| * quite a lot. |
| */ |
| if (_gtk_text_btree_char_is_invisible (&pos)) |
| vis_offset -= seg->char_count; |
| |
| _gtk_text_iter_forward_indexable_segment (&pos); |
| |
| seg = _gtk_text_iter_get_indexable_segment (&pos); |
| } |
| |
| if (_gtk_text_btree_char_is_invisible (&pos)) |
| vis_offset -= real->segment_char_offset; |
| |
| return vis_offset; |
| } |
| |
| |
| /** |
| * gtk_text_iter_get_visible_line_index: |
| * @iter: a #GtkTextIter |
| * |
| * Returns the number of bytes from the start of the |
| * line to the given @iter, not counting bytes that |
| * are invisible due to tags with the “invisible” flag |
| * toggled on. |
| * |
| * Returns: byte index of @iter with respect to the start of the line |
| **/ |
| gint |
| gtk_text_iter_get_visible_line_index (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| gint vis_offset; |
| GtkTextLineSegment *seg; |
| GtkTextIter pos; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| ensure_byte_offsets (real); |
| |
| check_invariants (iter); |
| |
| vis_offset = real->line_byte_offset; |
| |
| g_assert (vis_offset >= 0); |
| |
| _gtk_text_btree_get_iter_at_line (real->tree, |
| &pos, |
| real->line, |
| 0); |
| |
| seg = _gtk_text_iter_get_indexable_segment (&pos); |
| |
| while (seg != real->segment) |
| { |
| /* This is a pretty expensive call, making the |
| * whole function pretty lame; we could keep track |
| * of current invisibility state by looking at toggle |
| * segments as we loop, and then call this function |
| * only once per line, in order to speed up the loop |
| * quite a lot. |
| */ |
| if (_gtk_text_btree_char_is_invisible (&pos)) |
| vis_offset -= seg->byte_count; |
| |
| _gtk_text_iter_forward_indexable_segment (&pos); |
| |
| seg = _gtk_text_iter_get_indexable_segment (&pos); |
| } |
| |
| if (_gtk_text_btree_char_is_invisible (&pos)) |
| vis_offset -= real->segment_byte_offset; |
| |
| return vis_offset; |
| } |
| |
| /* |
| * Dereferencing |
| */ |
| |
| /** |
| * gtk_text_iter_get_char: |
| * @iter: an iterator |
| * |
| * The Unicode character at this iterator is returned. (Equivalent to |
| * operator* on a C++ iterator.) If the element at this iterator is a |
| * non-character element, such as an image embedded in the buffer, the |
| * Unicode “unknown” character 0xFFFC is returned. If invoked on |
| * the end iterator, zero is returned; zero is not a valid Unicode character. |
| * So you can write a loop which ends when gtk_text_iter_get_char() |
| * returns 0. |
| * |
| * Returns: a Unicode character, or 0 if @iter is not dereferenceable |
| */ |
| gunichar |
| gtk_text_iter_get_char (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| check_invariants (iter); |
| |
| if (gtk_text_iter_is_end (iter)) |
| return 0; |
| else if (real->segment->type == >k_text_char_type) |
| { |
| ensure_byte_offsets (real); |
| |
| return g_utf8_get_char (real->segment->body.chars + |
| real->segment_byte_offset); |
| } |
| else |
| { |
| /* Unicode "unknown character" 0xFFFC */ |
| return GTK_TEXT_UNKNOWN_CHAR; |
| } |
| } |
| |
| /** |
| * gtk_text_iter_get_slice: |
| * @start: iterator at start of a range |
| * @end: iterator at end of a range |
| * |
| * Returns the text in the given range. A “slice” is an array of |
| * characters encoded in UTF-8 format, including the Unicode “unknown” |
| * character 0xFFFC for iterable non-character elements in the buffer, |
| * such as images. Because images are encoded in the slice, byte and |
| * character offsets in the returned array will correspond to byte |
| * offsets in the text buffer. Note that 0xFFFC can occur in normal |
| * text as well, so it is not a reliable indicator that a texture or |
| * widget is in the buffer. |
| * |
| * Returns: (transfer full): slice of text from the buffer |
| **/ |
| gchar* |
| gtk_text_iter_get_slice (const GtkTextIter *start, |
| const GtkTextIter *end) |
| { |
| g_return_val_if_fail (start != NULL, NULL); |
| g_return_val_if_fail (end != NULL, NULL); |
| |
| check_invariants (start); |
| check_invariants (end); |
| |
| return _gtk_text_btree_get_text (start, end, TRUE, TRUE); |
| } |
| |
| /** |
| * gtk_text_iter_get_text: |
| * @start: iterator at start of a range |
| * @end: iterator at end of a range |
| * |
| * Returns text in the given range. If the range |
| * contains non-text elements such as images, the character and byte |
| * offsets in the returned string will not correspond to character and |
| * byte offsets in the buffer. If you want offsets to correspond, see |
| * gtk_text_iter_get_slice(). |
| * |
| * Returns: (transfer full): array of characters from the buffer |
| **/ |
| gchar* |
| gtk_text_iter_get_text (const GtkTextIter *start, |
| const GtkTextIter *end) |
| { |
| g_return_val_if_fail (start != NULL, NULL); |
| g_return_val_if_fail (end != NULL, NULL); |
| |
| check_invariants (start); |
| check_invariants (end); |
| |
| return _gtk_text_btree_get_text (start, end, TRUE, FALSE); |
| } |
| |
| /** |
| * gtk_text_iter_get_visible_slice: |
| * @start: iterator at start of range |
| * @end: iterator at end of range |
| * |
| * Like gtk_text_iter_get_slice(), but invisible text is not included. |
| * Invisible text is usually invisible because a #GtkTextTag with the |
| * “invisible” attribute turned on has been applied to it. |
| * |
| * Returns: (transfer full): slice of text from the buffer |
| **/ |
| gchar* |
| gtk_text_iter_get_visible_slice (const GtkTextIter *start, |
| const GtkTextIter *end) |
| { |
| g_return_val_if_fail (start != NULL, NULL); |
| g_return_val_if_fail (end != NULL, NULL); |
| |
| check_invariants (start); |
| check_invariants (end); |
| |
| return _gtk_text_btree_get_text (start, end, FALSE, TRUE); |
| } |
| |
| /** |
| * gtk_text_iter_get_visible_text: |
| * @start: iterator at start of range |
| * @end: iterator at end of range |
| * |
| * Like gtk_text_iter_get_text(), but invisible text is not included. |
| * Invisible text is usually invisible because a #GtkTextTag with the |
| * “invisible” attribute turned on has been applied to it. |
| * |
| * Returns: (transfer full): string containing visible text in the |
| * range |
| **/ |
| gchar* |
| gtk_text_iter_get_visible_text (const GtkTextIter *start, |
| const GtkTextIter *end) |
| { |
| g_return_val_if_fail (start != NULL, NULL); |
| g_return_val_if_fail (end != NULL, NULL); |
| |
| check_invariants (start); |
| check_invariants (end); |
| |
| return _gtk_text_btree_get_text (start, end, FALSE, FALSE); |
| } |
| |
| /** |
| * gtk_text_iter_get_texture: |
| * @iter: an iterator |
| * |
| * If the element at @iter is a texture, the texture is returned |
| * (with no new reference count added). Otherwise, %NULL is returned. |
| * |
| * Returns: (transfer none): the texture at @iter |
| **/ |
| GdkTexture * |
| gtk_text_iter_get_texture (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return NULL; |
| |
| check_invariants (iter); |
| |
| if (real->segment->type != >k_text_texture_type) |
| return NULL; |
| else |
| return real->segment->body.texture.texture; |
| } |
| |
| /** |
| * gtk_text_iter_get_child_anchor: |
| * @iter: an iterator |
| * |
| * If the location at @iter contains a child anchor, the |
| * anchor is returned (with no new reference count added). Otherwise, |
| * %NULL is returned. |
| * |
| * Returns: (transfer none): the anchor at @iter |
| **/ |
| GtkTextChildAnchor* |
| gtk_text_iter_get_child_anchor (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return NULL; |
| |
| check_invariants (iter); |
| |
| if (real->segment->type != >k_text_child_type) |
| return NULL; |
| else |
| return real->segment->body.child.obj; |
| } |
| |
| /** |
| * gtk_text_iter_get_marks: |
| * @iter: an iterator |
| * |
| * Returns a list of all #GtkTextMark at this location. Because marks |
| * are not iterable (they don’t take up any "space" in the buffer, |
| * they are just marks in between iterable locations), multiple marks |
| * can exist in the same place. The returned list is not in any |
| * meaningful order. |
| * |
| * Returns: (element-type GtkTextMark) (transfer container): list of #GtkTextMark |
| **/ |
| GSList* |
| gtk_text_iter_get_marks (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| GtkTextLineSegment *seg; |
| GSList *retval; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return NULL; |
| |
| check_invariants (iter); |
| |
| retval = NULL; |
| seg = real->any_segment; |
| while (seg != real->segment) |
| { |
| if (seg->type == >k_text_left_mark_type || |
| seg->type == >k_text_right_mark_type) |
| retval = g_slist_prepend (retval, seg->body.mark.obj); |
| |
| seg = seg->next; |
| } |
| |
| /* The returned list isn't guaranteed to be in any special order, |
| and it isn't. */ |
| return retval; |
| } |
| |
| /** |
| * gtk_text_iter_get_toggled_tags: |
| * @iter: an iterator |
| * @toggled_on: %TRUE to get toggled-on tags |
| * |
| * Returns a list of #GtkTextTag that are toggled on or off at this |
| * point. (If @toggled_on is %TRUE, the list contains tags that are |
| * toggled on.) If a tag is toggled on at @iter, then some non-empty |
| * range of characters following @iter has that tag applied to it. If |
| * a tag is toggled off, then some non-empty range following @iter |
| * does not have the tag applied to it. |
| * |
| * Returns: (element-type GtkTextTag) (transfer container): tags toggled at this point |
| **/ |
| GSList* |
| gtk_text_iter_get_toggled_tags (const GtkTextIter *iter, |
| gboolean toggled_on) |
| { |
| GtkTextRealIter *real; |
| GtkTextLineSegment *seg; |
| GSList *retval; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return NULL; |
| |
| check_invariants (iter); |
| |
| retval = NULL; |
| seg = real->any_segment; |
| while (seg != real->segment) |
| { |
| if (toggled_on) |
| { |
| if (seg->type == >k_text_toggle_on_type) |
| { |
| retval = g_slist_prepend (retval, seg->body.toggle.info->tag); |
| } |
| } |
| else |
| { |
| if (seg->type == >k_text_toggle_off_type) |
| { |
| retval = g_slist_prepend (retval, seg->body.toggle.info->tag); |
| } |
| } |
| |
| seg = seg->next; |
| } |
| |
| /* The returned list isn't guaranteed to be in any special order, |
| and it isn't. */ |
| return retval; |
| } |
| |
| /** |
| * gtk_text_iter_starts_tag: |
| * @iter: an iterator |
| * @tag: (nullable): 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 that if gtk_text_iter_starts_tag() returns %TRUE, it means that @iter is |
| * at the beginning of the tagged range, and that the |
| * character at @iter is inside the tagged range. In other |
| * words, unlike gtk_text_iter_ends_tag(), if gtk_text_iter_starts_tag() returns |
| * %TRUE, gtk_text_iter_has_tag() will also return %TRUE for the same |
| * parameters. |
| * |
| * Returns: whether @iter is the start of a range tagged with @tag |
| **/ |
| gboolean |
| gtk_text_iter_starts_tag (const GtkTextIter *iter, |
| GtkTextTag *tag) |
| { |
| GtkTextRealIter *real; |
| GtkTextLineSegment *seg; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| seg = real->any_segment; |
| while (seg != real->segment) |
| { |
| if (seg->type == >k_text_toggle_on_type) |
| { |
| if (tag == NULL || |
| seg->body.toggle.info->tag == tag) |
| return TRUE; |
| } |
| |
| seg = seg->next; |
| } |
| |
| return FALSE; |
| } |
| |
| /** |
| * gtk_text_iter_ends_tag: |
| * @iter: an iterator |
| * @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 that if gtk_text_iter_ends_tag() returns %TRUE, it means that @iter is |
| * at the end of the tagged range, but that the character |
| * at @iter is outside the tagged range. In other words, |
| * unlike gtk_text_iter_starts_tag(), if gtk_text_iter_ends_tag() returns %TRUE, |
| * gtk_text_iter_has_tag() will return %FALSE for the same parameters. |
| * |
| * Returns: whether @iter is the end of a range tagged with @tag |
| **/ |
| gboolean |
| gtk_text_iter_ends_tag (const GtkTextIter *iter, |
| GtkTextTag *tag) |
| { |
| GtkTextRealIter *real; |
| GtkTextLineSegment *seg; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| seg = real->any_segment; |
| while (seg != real->segment) |
| { |
| if (seg->type == >k_text_toggle_off_type) |
| { |
| if (tag == NULL || |
| seg->body.toggle.info->tag == tag) |
| return TRUE; |
| } |
| |
| seg = seg->next; |
| } |
| |
| return FALSE; |
| } |
| |
| /** |
| * gtk_text_iter_toggles_tag: |
| * @iter: an iterator |
| * @tag: (allow-none): a #GtkTextTag, or %NULL |
| * |
| * This is equivalent to (gtk_text_iter_starts_tag() || |
| * gtk_text_iter_ends_tag()), i.e. it tells you whether a range with |
| * @tag applied to it begins or ends at @iter. |
| * |
| * Returns: whether @tag is toggled on or off at @iter |
| **/ |
| gboolean |
| gtk_text_iter_toggles_tag (const GtkTextIter *iter, |
| GtkTextTag *tag) |
| { |
| GtkTextRealIter *real; |
| GtkTextLineSegment *seg; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| seg = real->any_segment; |
| while (seg != real->segment) |
| { |
| if ( (seg->type == >k_text_toggle_off_type || |
| seg->type == >k_text_toggle_on_type) && |
| (tag == NULL || |
| seg->body.toggle.info->tag == tag) ) |
| return TRUE; |
| |
| seg = seg->next; |
| } |
| |
| return FALSE; |
| } |
| |
| /** |
| * gtk_text_iter_has_tag: |
| * @iter: an iterator |
| * @tag: a #GtkTextTag |
| * |
| * Returns %TRUE if @iter points to a character that is part of a range tagged |
| * with @tag. See also gtk_text_iter_starts_tag() and gtk_text_iter_ends_tag(). |
| * |
| * Returns: whether @iter is tagged with @tag |
| **/ |
| gboolean |
| gtk_text_iter_has_tag (const GtkTextIter *iter, |
| GtkTextTag *tag) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), FALSE); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| if (real->line_byte_offset >= 0) |
| { |
| return _gtk_text_line_byte_has_tag (real->line, real->tree, |
| real->line_byte_offset, tag); |
| } |
| else |
| { |
| g_assert (real->line_char_offset >= 0); |
| return _gtk_text_line_char_has_tag (real->line, real->tree, |
| real->line_char_offset, tag); |
| } |
| } |
| |
| /** |
| * gtk_text_iter_get_tags: |
| * @iter: a #GtkTextIter |
| * |
| * Returns a list of tags that apply to @iter, in ascending order of |
| * 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. |
| * |
| * Returns: (element-type GtkTextTag) (transfer container): list of #GtkTextTag |
| **/ |
| GSList* |
| gtk_text_iter_get_tags (const GtkTextIter *iter) |
| { |
| GtkTextTag** tags; |
| gint tag_count = 0; |
| gint i; |
| GSList *retval; |
| |
| g_return_val_if_fail (iter != NULL, NULL); |
| |
| /* Get the tags at this spot */ |
| tags = _gtk_text_btree_get_tags (iter, &tag_count); |
| |
| /* No tags, use default style */ |
| if (tags == NULL || tag_count == 0) |
| { |
| g_free (tags); |
| |
| return NULL; |
| } |
| |
| retval = NULL; |
| i = 0; |
| while (i < tag_count) |
| { |
| retval = g_slist_prepend (retval, tags[i]); |
| ++i; |
| } |
| |
| g_free (tags); |
| |
| /* Return tags in ascending order of priority */ |
| return g_slist_reverse (retval); |
| } |
| |
| /** |
| * gtk_text_iter_editable: |
| * @iter: an iterator |
| * @default_setting: %TRUE if text is editable by default |
| * |
| * Returns whether the character at @iter is within an editable region |
| * of text. Non-editable text is “locked” and can’t be changed by the |
| * user via #GtkTextView. This function is simply a convenience |
| * wrapper around gtk_text_iter_get_attributes(). If no tags applied |
| * to this text affect editability, @default_setting will be returned. |
| * |
| * You don’t want to use this function to decide whether text can be |
| * inserted at @iter, because for insertion you don’t want to know |
| * whether the char at @iter is inside an editable range, you want to |
| * know whether a new character inserted at @iter would be inside an |
| * editable range. Use gtk_text_iter_can_insert() to handle this |
| * case. |
| * |
| * Returns: whether @iter is inside an editable range |
| **/ |
| gboolean |
| gtk_text_iter_editable (const GtkTextIter *iter, |
| gboolean default_setting) |
| { |
| GtkTextAttributes *values; |
| gboolean retval; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| values = gtk_text_attributes_new (); |
| |
| values->editable = default_setting; |
| |
| gtk_text_iter_get_attributes (iter, values); |
| |
| retval = values->editable; |
| |
| gtk_text_attributes_unref (values); |
| |
| return retval; |
| } |
| |
| /** |
| * gtk_text_iter_can_insert: |
| * @iter: an iterator |
| * @default_editability: %TRUE if text is editable by default |
| * |
| * Considering the default editability of the buffer, and tags that |
| * affect editability, determines whether text inserted at @iter would |
| * be editable. If text inserted at @iter would be editable then the |
| * user should be allowed to insert text at @iter. |
| * gtk_text_buffer_insert_interactive() uses this function to decide |
| * whether insertions are allowed at a given position. |
| * |
| * Returns: whether text inserted at @iter would be editable |
| **/ |
| gboolean |
| gtk_text_iter_can_insert (const GtkTextIter *iter, |
| gboolean default_editability) |
| { |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| if (gtk_text_iter_editable (iter, default_editability)) |
| return TRUE; |
| /* If at start/end of buffer, default editability is used */ |
| else if ((gtk_text_iter_is_start (iter) || |
| gtk_text_iter_is_end (iter)) && |
| default_editability) |
| return TRUE; |
| else |
| { |
| /* if iter isn't editable, and the char before iter is, |
| * then iter is the first char in an editable region |
| * and thus insertion at iter results in editable text. |
| */ |
| GtkTextIter prev = *iter; |
| gtk_text_iter_backward_char (&prev); |
| return gtk_text_iter_editable (&prev, default_editability); |
| } |
| } |
| |
| gboolean |
| gtk_text_iter_get_attributes (const GtkTextIter *iter, |
| GtkTextAttributes *values) |
| { |
| GtkTextTag** tags; |
| gint tag_count = 0; |
| |
| /* Get the tags at this spot */ |
| tags = _gtk_text_btree_get_tags (iter, &tag_count); |
| |
| /* No tags, use default style */ |
| if (tags == NULL || tag_count == 0) |
| { |
| g_free (tags); |
| |
| return FALSE; |
| } |
| |
| _gtk_text_attributes_fill_from_tags (values, |
| tags, |
| tag_count); |
| |
| g_free (tags); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gtk_text_iter_get_language: |
| * @iter: an iterator |
| * |
| * A convenience wrapper around gtk_text_iter_get_attributes(), |
| * which returns the language in effect at @iter. If no tags affecting |
| * language apply to @iter, the return value is identical to that of |
| * gtk_get_default_language(). |
| * |
| * Returns: (transfer full): language in effect at @iter |
| **/ |
| PangoLanguage * |
| gtk_text_iter_get_language (const GtkTextIter *iter) |
| { |
| GtkTextAttributes *values; |
| PangoLanguage *retval; |
| |
| values = gtk_text_attributes_new (); |
| |
| gtk_text_iter_get_attributes (iter, values); |
| |
| retval = values->language; |
| |
| gtk_text_attributes_unref (values); |
| |
| return retval; |
| } |
| |
| /** |
| * gtk_text_iter_starts_line: |
| * @iter: an iterator |
| * |
| * Returns %TRUE if @iter begins a paragraph, |
| * i.e. if gtk_text_iter_get_line_offset() would return 0. |
| * However this function is potentially more efficient than |
| * gtk_text_iter_get_line_offset() because it doesn’t have to compute |
| * the offset, it just has to see whether it’s 0. |
| * |
| * Returns: whether @iter begins a line |
| **/ |
| gboolean |
| gtk_text_iter_starts_line (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| if (real->line_byte_offset >= 0) |
| { |
| return (real->line_byte_offset == 0); |
| } |
| else |
| { |
| g_assert (real->line_char_offset >= 0); |
| return (real->line_char_offset == 0); |
| } |
| } |
| |
| /** |
| * gtk_text_iter_ends_line: |
| * @iter: an iterator |
| * |
| * Returns %TRUE if @iter points to the start of the paragraph |
| * delimiter characters for a line (delimiters will be either a |
| * newline, a carriage return, a carriage return followed by a |
| * newline, or a Unicode paragraph separator character). Note that an |
| * iterator pointing to the \n of a \r\n pair will not be counted as |
| * the end of a line, the line ends before the \r. The end iterator is |
| * considered to be at the end of a line, even though there are no |
| * paragraph delimiter chars there. |
| * |
| * Returns: whether @iter is at the end of a line |
| **/ |
| gboolean |
| gtk_text_iter_ends_line (const GtkTextIter *iter) |
| { |
| gunichar wc; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| check_invariants (iter); |
| |
| /* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in |
| * Unicode 3.0; update this if that changes. |
| */ |
| #define PARAGRAPH_SEPARATOR 0x2029 |
| |
| wc = gtk_text_iter_get_char (iter); |
| |
| if (wc == '\r' || wc == PARAGRAPH_SEPARATOR || wc == 0) /* wc == 0 is end iterator */ |
| 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. |
| * 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 ... |
| |
| */ |
| |
| if (gtk_text_iter_get_line_offset (&tmp) == 0) |
| return TRUE; |
| |
| if (!gtk_text_iter_backward_char (&tmp)) |
| return TRUE; |
| |
| return gtk_text_iter_get_char (&tmp) != '\r'; |
| } |
| else |
| return FALSE; |
| } |
| |
| /** |
| * gtk_text_iter_is_end: |
| * @iter: an iterator |
| * |
| * Returns %TRUE if @iter is the end iterator, i.e. one past the last |
| * dereferenceable iterator in the buffer. gtk_text_iter_is_end() is |
| * the most efficient way to check whether an iterator is the end |
| * iterator. |
| * |
| * Returns: whether @iter is the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_is_end (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| if (!_gtk_text_line_contains_end_iter (real->line, real->tree)) |
| return FALSE; |
| |
| /* Now we need the segments validated */ |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| return _gtk_text_btree_is_end (real->tree, real->line, |
| real->segment, |
| real->segment_byte_offset, |
| real->segment_char_offset); |
| } |
| |
| /** |
| * gtk_text_iter_is_start: |
| * @iter: an iterator |
| * |
| * Returns %TRUE if @iter is the first iterator in the buffer, that is |
| * if @iter has a character offset of 0. |
| * |
| * Returns: whether @iter is the first in the buffer |
| **/ |
| gboolean |
| gtk_text_iter_is_start (const GtkTextIter *iter) |
| { |
| return gtk_text_iter_get_offset (iter) == 0; |
| } |
| |
| /** |
| * gtk_text_iter_get_chars_in_line: |
| * @iter: an iterator |
| * |
| * Returns the number of characters in the line containing @iter, |
| * including the paragraph delimiters. |
| * |
| * Returns: number of characters in the line |
| **/ |
| gint |
| gtk_text_iter_get_chars_in_line (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| gint count; |
| GtkTextLineSegment *seg; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| check_invariants (iter); |
| |
| if (real->line_char_offset >= 0) |
| { |
| /* We can start at the segments we've already found. */ |
| count = real->line_char_offset - real->segment_char_offset; |
| seg = _gtk_text_iter_get_indexable_segment (iter); |
| } |
| else |
| { |
| /* count whole line. */ |
| seg = real->line->segments; |
| count = 0; |
| } |
| |
| |
| while (seg != NULL) |
| { |
| count += seg->char_count; |
| |
| seg = seg->next; |
| } |
| |
| if (_gtk_text_line_contains_end_iter (real->line, real->tree)) |
| count -= 1; /* Dump the newline that was in the last segment of the end iter line */ |
| |
| return count; |
| } |
| |
| /** |
| * gtk_text_iter_get_bytes_in_line: |
| * @iter: an iterator |
| * |
| * Returns the number of bytes in the line containing @iter, |
| * including the paragraph delimiters. |
| * |
| * Returns: number of bytes in the line |
| **/ |
| gint |
| gtk_text_iter_get_bytes_in_line (const GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| gint count; |
| GtkTextLineSegment *seg; |
| |
| g_return_val_if_fail (iter != NULL, 0); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return 0; |
| |
| check_invariants (iter); |
| |
| if (real->line_byte_offset >= 0) |
| { |
| /* We can start at the segments we've already found. */ |
| count = real->line_byte_offset - real->segment_byte_offset; |
| seg = _gtk_text_iter_get_indexable_segment (iter); |
| } |
| else |
| { |
| /* count whole line. */ |
| seg = real->line->segments; |
| count = 0; |
| } |
| |
| while (seg != NULL) |
| { |
| count += seg->byte_count; |
| |
| seg = seg->next; |
| } |
| |
| if (_gtk_text_line_contains_end_iter (real->line, real->tree)) |
| count -= 1; /* Dump the newline that was in the last segment of the end iter line */ |
| |
| return count; |
| } |
| |
| /* |
| * Increments/decrements |
| */ |
| |
| /* The return value of this indicates WHETHER WE MOVED. |
| * The return value of public functions indicates |
| * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE) |
| * |
| * This function will not change the iterator if |
| * it’s already on the last (end iter) line, i.e. it |
| * won’t move to the end of the last line. |
| */ |
| static gboolean |
| forward_line_leaving_caches_unmodified (GtkTextRealIter *real) |
| { |
| if (!_gtk_text_line_contains_end_iter (real->line, real->tree)) |
| { |
| GtkTextLine *new_line; |
| |
| new_line = _gtk_text_line_next (real->line); |
| g_assert (new_line); |
| g_assert (new_line != real->line); |
| g_assert (!_gtk_text_line_is_last (new_line, real->tree)); |
| |
| real->line = new_line; |
| |
| real->line_byte_offset = 0; |
| real->line_char_offset = 0; |
| |
| real->segment_byte_offset = 0; |
| real->segment_char_offset = 0; |
| |
| /* Find first segments in new line */ |
| real->any_segment = real->line->segments; |
| real->segment = real->any_segment; |
| while (real->segment->char_count == 0) |
| real->segment = real->segment->next; |
| |
| return TRUE; |
| } |
| else |
| { |
| /* There is no way to move forward a line; we were already at |
| * the line containing the end iterator. |
| * However we may not be at the end iterator itself. |
| */ |
| |
| return FALSE; |
| } |
| } |
| |
| #if 0 |
| /* The return value of this indicates WHETHER WE MOVED. |
| * The return value of public functions indicates |
| * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE) |
| * |
| * This function is currently unused, thus it is #if-0-ed. It is |
| * left here, since it’s non-trivial code that might be useful in |
| * the future. |
| */ |
| static gboolean |
| backward_line_leaving_caches_unmodified (GtkTextRealIter *real) |
| { |
| GtkTextLine *new_line; |
| |
| new_line = _gtk_text_line_previous (real->line); |
| |
| g_assert (new_line != real->line); |
| |
| if (new_line != NULL) |
| { |
| real->line = new_line; |
| |
| real->line_byte_offset = 0; |
| real->line_char_offset = 0; |
| |
| real->segment_byte_offset = 0; |
| real->segment_char_offset = 0; |
| |
| /* Find first segments in new line */ |
| real->any_segment = real->line->segments; |
| real->segment = real->any_segment; |
| while (real->segment->char_count == 0) |
| real->segment = real->segment->next; |
| |
| return TRUE; |
| } |
| else |
| { |
| /* There is no way to move backward; we were already |
| at the first line. */ |
| |
| /* We leave real->line as-is */ |
| |
| /* Note that we didn't clamp to the start of the first line. */ |
| |
| return FALSE; |
| } |
| } |
| #endif |
| |
| /* The return value indicates (MOVEMENT OCCURRED && NEW ITER IS |
| * DEREFERENCEABLE) |
| */ |
| static gboolean |
| forward_char (GtkTextRealIter *real) |
| { |
| GtkTextIter *iter = (GtkTextIter*)real; |
| |
| check_invariants ((GtkTextIter*)real); |
| |
| ensure_char_offsets (real); |
| |
| if ( (real->segment_char_offset + 1) == real->segment->char_count) |
| { |
| /* Need to move to the next segment; if no next segment, |
| need to move to next line. */ |
| return _gtk_text_iter_forward_indexable_segment (iter); |
| } |
| else |
| { |
| /* Just moving within a segment. Keep byte count |
| up-to-date, if it was already up-to-date. */ |
| |
| g_assert (real->segment->type == >k_text_char_type); |
| |
| if (real->line_byte_offset >= 0) |
| { |
| gint bytes; |
| const char * start = |
| real->segment->body.chars + real->segment_byte_offset; |
| |
| bytes = g_utf8_next_char (start) - start; |
| |
| real->line_byte_offset += bytes; |
| real->segment_byte_offset += bytes; |
| |
| g_assert (real->segment_byte_offset < real->segment->byte_count); |
| } |
| |
| real->line_char_offset += 1; |
| real->segment_char_offset += 1; |
| |
| adjust_char_index (real, 1); |
| |
| g_assert (real->segment_char_offset < real->segment->char_count); |
| |
| /* We moved into the middle of a segment, so the any_segment |
| must now be the segment we're in the middle of. */ |
| real->any_segment = real->segment; |
| |
| check_invariants ((GtkTextIter*)real); |
| |
| if (gtk_text_iter_is_end ((GtkTextIter*)real)) |
| return FALSE; |
| else |
| return TRUE; |
| } |
| } |
| |
| gboolean |
| _gtk_text_iter_forward_indexable_segment (GtkTextIter *iter) |
| { |
| /* Need to move to the next segment; if no next segment, |
| need to move to next line. */ |
| GtkTextLineSegment *seg; |
| GtkTextLineSegment *any_seg; |
| GtkTextRealIter *real; |
| gint chars_skipped; |
| gint bytes_skipped; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| if (real->line_char_offset >= 0) |
| { |
| chars_skipped = real->segment->char_count - real->segment_char_offset; |
| g_assert (chars_skipped > 0); |
| } |
| else |
| chars_skipped = 0; |
| |
| if (real->line_byte_offset >= 0) |
| { |
| bytes_skipped = real->segment->byte_count - real->segment_byte_offset; |
| g_assert (bytes_skipped > 0); |
| } |
| else |
| bytes_skipped = 0; |
| |
| /* Get first segment of any kind */ |
| any_seg = real->segment->next; |
| /* skip non-indexable segments, if any */ |
| seg = any_seg; |
| while (seg != NULL && seg->char_count == 0) |
| seg = seg->next; |
| |
| if (seg != NULL) |
| { |
| real->any_segment = any_seg; |
| real->segment = seg; |
| |
| if (real->line_byte_offset >= 0) |
| { |
| g_assert (bytes_skipped > 0); |
| real->segment_byte_offset = 0; |
| real->line_byte_offset += bytes_skipped; |
| } |
| |
| if (real->line_char_offset >= 0) |
| { |
| g_assert (chars_skipped > 0); |
| real->segment_char_offset = 0; |
| real->line_char_offset += chars_skipped; |
| adjust_char_index (real, chars_skipped); |
| } |
| |
| check_invariants (iter); |
| |
| return !gtk_text_iter_is_end (iter); |
| } |
| else |
| { |
| /* End of the line */ |
| if (forward_line_leaving_caches_unmodified (real)) |
| { |
| adjust_line_number (real, 1); |
| if (real->line_char_offset >= 0) |
| adjust_char_index (real, chars_skipped); |
| |
| g_assert (real->line_byte_offset == 0); |
| g_assert (real->line_char_offset == 0); |
| g_assert (real->segment_byte_offset == 0); |
| g_assert (real->segment_char_offset == 0); |
| g_assert (gtk_text_iter_starts_line (iter)); |
| |
| check_invariants (iter); |
| |
| return !gtk_text_iter_is_end (iter); |
| } |
| else |
| { |
| /* End of buffer, but iter is still at start of last segment, |
| * not at the end iterator. We put it on the end iterator. |
| */ |
| |
| check_invariants (iter); |
| |
| g_assert (!_gtk_text_line_is_last (real->line, real->tree)); |
| g_assert (_gtk_text_line_contains_end_iter (real->line, real->tree)); |
| |
| gtk_text_iter_forward_to_line_end (iter); |
| |
| g_assert (gtk_text_iter_is_end (iter)); |
| |
| return FALSE; |
| } |
| } |
| } |
| |
| static gboolean |
| at_last_indexable_segment (GtkTextRealIter *real) |
| { |
| GtkTextLineSegment *seg; |
| |
| /* Return TRUE if there are no indexable segments after |
| * this iterator. |
| */ |
| |
| seg = real->segment->next; |
| while (seg) |
| { |
| if (seg->char_count > 0) |
| return FALSE; |
| seg = seg->next; |
| } |
| return TRUE; |
| } |
| |
| /* Goes back to the start of the next segment, even if |
| * we’re not at the start of the current segment (always |
| * ends up on a different segment if it returns TRUE) |
| */ |
| gboolean |
| _gtk_text_iter_backward_indexable_segment (GtkTextIter *iter) |
| { |
| /* Move to the start of the previous segment; if no previous |
| * segment, to the last segment in the previous line. This is |
| * inherently a bit inefficient due to the singly-linked list and |
| * tree nodes, but we can't afford the RAM for doubly-linked. |
| */ |
| GtkTextRealIter *real; |
| GtkTextLineSegment *seg; |
| GtkTextLineSegment *any_seg; |
| GtkTextLineSegment *prev_seg; |
| GtkTextLineSegment *prev_any_seg; |
| gint bytes_skipped; |
| gint chars_skipped; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| /* Find first segments in line */ |
| any_seg = real->line->segments; |
| seg = any_seg; |
| while (seg->char_count == 0) |
| seg = seg->next; |
| |
| if (seg == real->segment) |
| { |
| /* Could probably do this case faster by hand-coding the |
| * iteration. |
| */ |
| |
| /* We were already at the start of a line; |
| * go back to the previous line. |
| */ |
| if (gtk_text_iter_backward_line (iter)) |
| { |
| /* Go forward to last indexable segment in line. */ |
| while (!at_last_indexable_segment (real)) |
| _gtk_text_iter_forward_indexable_segment (iter); |
| |
| check_invariants (iter); |
| |
| return TRUE; |
| } |
| else |
| return FALSE; /* We were at the start of the first line. */ |
| } |
| |
| /* We must be in the middle of a line; so find the indexable |
| * segment just before our current segment. |
| */ |
| g_assert (seg != real->segment); |
| do |
| { |
| prev_seg = seg; |
| prev_any_seg = any_seg; |
| |
| any_seg = seg->next; |
| seg = any_seg; |
| while (seg->char_count == 0) |
| seg = seg->next; |
| } |
| while (seg != real->segment); |
| |
| g_assert (prev_seg != NULL); |
| g_assert (prev_any_seg != NULL); |
| g_assert (prev_seg->char_count > 0); |
| |
| /* We skipped the entire previous segment, plus any |
| * chars we were into the current segment. |
| */ |
| if (real->segment_byte_offset >= 0) |
| bytes_skipped = prev_seg->byte_count + real->segment_byte_offset; |
| else |
| bytes_skipped = -1; |
| |
| if (real->segment_char_offset >= 0) |
| chars_skipped = prev_seg->char_count + real->segment_char_offset; |
| else |
| chars_skipped = -1; |
| |
| real->segment = prev_seg; |
| real->any_segment = prev_any_seg; |
| real->segment_byte_offset = 0; |
| real->segment_char_offset = 0; |
| |
| if (bytes_skipped >= 0) |
| { |
| if (real->line_byte_offset >= 0) |
| { |
| real->line_byte_offset -= bytes_skipped; |
| g_assert (real->line_byte_offset >= 0); |
| } |
| } |
| else |
| real->line_byte_offset = -1; |
| |
| if (chars_skipped >= 0) |
| { |
| if (real->line_char_offset >= 0) |
| { |
| real->line_char_offset -= chars_skipped; |
| g_assert (real->line_char_offset >= 0); |
| } |
| |
| if (real->cached_char_index >= 0) |
| { |
| real->cached_char_index -= chars_skipped; |
| g_assert (real->cached_char_index >= 0); |
| } |
| } |
| else |
| { |
| real->line_char_offset = -1; |
| real->cached_char_index = -1; |
| } |
| |
| /* line number is unchanged. */ |
| |
| check_invariants (iter); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gtk_text_iter_forward_char: |
| * @iter: an iterator |
| * |
| * Moves @iter forward by one character offset. Note that images |
| * embedded in the buffer occupy 1 character slot, so |
| * gtk_text_iter_forward_char() may actually move onto an image instead |
| * of a character, if you have images in your buffer. If @iter is the |
| * end iterator or one character before it, @iter will now point at |
| * the end iterator, and gtk_text_iter_forward_char() returns %FALSE for |
| * convenience when writing loops. |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_forward_char (GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| else |
| { |
| check_invariants (iter); |
| return forward_char (real); |
| } |
| } |
| |
| /** |
| * gtk_text_iter_backward_char: |
| * @iter: an iterator |
| * |
| * Moves backward by one character offset. Returns %TRUE if movement |
| * was possible; if @iter was the first in the buffer (character |
| * offset 0), gtk_text_iter_backward_char() returns %FALSE for convenience when |
| * writing loops. |
| * |
| * Returns: whether movement was possible |
| **/ |
| gboolean |
| gtk_text_iter_backward_char (GtkTextIter *iter) |
| { |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| check_invariants (iter); |
| |
| return gtk_text_iter_backward_chars (iter, 1); |
| } |
| |
| /* |
| Definitely we should try to linear scan as often as possible for |
| movement within a single line, because we can't use the BTree to |
| speed within-line searches up; for movement between lines, we would |
| like to avoid the linear scan probably. |
| |
| Instead of using this constant, it might be nice to cache the line |
| length in the iterator and linear scan if motion is within a single |
| line. |
| |
| I guess you'd have to profile the various approaches. |
| */ |
| #define MAX_LINEAR_SCAN 150 |
| |
| |
| /** |
| * gtk_text_iter_forward_chars: |
| * @iter: an iterator |
| * @count: number of characters to move, may be negative |
| * |
| * Moves @count characters 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 new position of |
| * @iter is different from its original position, and dereferenceable |
| * (the last iterator in the buffer is not dereferenceable). If @count |
| * is 0, the function does nothing and returns %FALSE. |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_forward_chars (GtkTextIter *iter, gint count) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| FIX_OVERFLOWS (count); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| else if (count == 0) |
| return FALSE; |
| else if (count < 0) |
| return gtk_text_iter_backward_chars (iter, 0 - count); |
| else if (count < MAX_LINEAR_SCAN) |
| { |
| check_invariants (iter); |
| |
| while (count > 1) |
| { |
| if (!forward_char (real)) |
| return FALSE; |
| --count; |
| } |
| |
| return forward_char (real); |
| } |
| else |
| { |
| gint current_char_index; |
| gint new_char_index; |
| |
| check_invariants (iter); |
| |
| current_char_index = gtk_text_iter_get_offset (iter); |
| |
| if (current_char_index == _gtk_text_btree_char_count (real->tree)) |
| return FALSE; /* can't move forward */ |
| |
| new_char_index = current_char_index + count; |
| gtk_text_iter_set_offset (iter, new_char_index); |
| |
| check_invariants (iter); |
| |
| /* Return FALSE if we're on the non-dereferenceable end |
| * iterator. |
| */ |
| if (gtk_text_iter_is_end (iter)) |
| return FALSE; |
| else |
| return TRUE; |
| } |
| } |
| |
| /** |
| * gtk_text_iter_backward_chars: |
| * @iter: an iterator |
| * @count: number of characters to move |
| * |
| * Moves @count characters 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. |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| * |
| **/ |
| gboolean |
| gtk_text_iter_backward_chars (GtkTextIter *iter, gint count) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| FIX_OVERFLOWS (count); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| else if (count == 0) |
| return FALSE; |
| else if (count < 0) |
| return gtk_text_iter_forward_chars (iter, 0 - count); |
| |
| ensure_char_offsets (real); |
| check_invariants (iter); |
| |
| /* <, not <=, because if count == segment_char_offset |
| * we're going to the front of the segment and the any_segment |
| * might change |
| */ |
| if (count < real->segment_char_offset) |
| { |
| /* Optimize the within-segment case */ |
| g_assert (real->segment->char_count > 0); |
| g_assert (real->segment->type == >k_text_char_type); |
| |
| if (real->line_byte_offset >= 0) |
| { |
| const char *p; |
| gint new_byte_offset; |
| |
| /* 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); |
| |
| check_invariants (iter); |
| |
| return TRUE; |
| } |
| else |
| { |
| /* We need to go back into previous segments. For now, |
| * just keep this really simple. FIXME |
| * use backward_indexable_segment. |
| */ |
| if (TRUE || count > MAX_LINEAR_SCAN) |
| { |
| gint current_char_index; |
| gint new_char_index; |
| |
| current_char_index = gtk_text_iter_get_offset (iter); |
| |
| if (current_char_index == 0) |
| return FALSE; /* can't move backward */ |
| |
| new_char_index = current_char_index - count; |
| if (new_char_index < 0) |
| new_char_index = 0; |
| |
| gtk_text_iter_set_offset (iter, new_char_index); |
| |
| check_invariants (iter); |
| |
| return TRUE; |
| } |
| else |
| { |
| /* FIXME backward_indexable_segment here */ |
| |
| return FALSE; |
| } |
| } |
| } |
| |
| #if 0 |
| |
| /* These two can't be implemented efficiently (always have to use |
| * a linear scan, since that’s the only way to find all the non-text |
| * segments) |
| */ |
| |
| /** |
| * gtk_text_iter_forward_text_chars: |
| * @iter: a #GtkTextIter |
| * @count: number of chars to move |
| * |
| * Moves forward by @count text characters (textures, widgets, |
| * etc. do not count as characters for this). Equivalent to moving |
| * through the results of gtk_text_iter_get_text(), rather than |
| * gtk_text_iter_get_slice(). |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_forward_text_chars (GtkTextIter *iter, |
| gint count) |
| { |
| |
| |
| |
| } |
| |
| /** |
| * gtk_text_iter_backward_text_chars: |
| * @iter: a #GtkTextIter |
| * @count: number of chars to move |
| * |
| * Moves backward by @count text characters (textures, widgets, |
| * etc. do not count as characters for this). Equivalent to moving |
| * through the results of gtk_text_iter_get_text(), rather than |
| * gtk_text_iter_get_slice(). |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_backward_text_chars (GtkTextIter *iter, |
| gint count) |
| { |
| |
| |
| } |
| #endif |
| |
| /** |
| * gtk_text_iter_forward_line: |
| * @iter: an iterator |
| * |
| * 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. |
| * |
| * Returns: whether @iter can be dereferenced |
| **/ |
| gboolean |
| gtk_text_iter_forward_line (GtkTextIter *iter) |
| { |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| if (forward_line_leaving_caches_unmodified (real)) |
| { |
| invalidate_char_index (real); |
| adjust_line_number (real, 1); |
| |
| check_invariants (iter); |
| |
| if (gtk_text_iter_is_end (iter)) |
| return FALSE; |
| else |
| return TRUE; |
| } |
| else |
| { |
| /* On the last line, move to end of it */ |
| |
| if (!gtk_text_iter_is_end (iter)) |
| gtk_text_iter_forward_to_end (iter); |
| |
| check_invariants (iter); |
| return FALSE; |
| } |
| } |
| |
| /** |
| * gtk_text_iter_backward_line: |
| * @iter: an iterator |
| * |
| * Moves @iter to the start of the previous 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.) |
| * |
| * Returns: whether @iter moved |
| **/ |
| gboolean |
| gtk_text_iter_backward_line (GtkTextIter *iter) |
| { |
| GtkTextLine *new_line; |
| GtkTextRealIter *real; |
| gboolean offset_will_change; |
| gint offset; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| ensure_char_offsets (real); |
| |
| check_invariants (iter); |
| |
| new_line = _gtk_text_line_previous (real->line); |
| |
| offset_will_change = FALSE; |
| if (real->line_char_offset > 0) |
| offset_will_change = TRUE; |
| |
| if (new_line != NULL) |
| { |
| real->line = new_line; |
| |
| adjust_line_number (real, -1); |
| } |
| else |
| { |
| if (!offset_will_change) |
| return FALSE; |
| } |
| |
| invalidate_char_index (real); |
| |
| real->line_byte_offset = 0; |
| real->line_char_offset = 0; |
| |
| real->segment_byte_offset = 0; |
| real->segment_char_offset = 0; |
| |
| /* Find first segment in line */ |
| real->any_segment = real->line->segments; |
| real->segment = _gtk_text_line_byte_to_segment (real->line, |
| 0, &offset); |
| |
| g_assert (offset == 0); |
| |
| /* Note that if we are on the first line, we snap to the start of |
| * the first line and return TRUE, so TRUE means the iterator |
| * changed, not that the line changed; this is maybe a bit |
| * weird. I'm not sure there's an obvious right thing to do though. |
| */ |
| |
| check_invariants (iter); |
| |
| return TRUE; |
| } |
| |
| |
| /** |
| * gtk_text_iter_forward_lines: |
| * @iter: a #GtkTextIter |
| * @count: number of lines to move forward |
| * |
| * Moves @count 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. |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_forward_lines (GtkTextIter *iter, gint count) |
| { |
| FIX_OVERFLOWS (count); |
| |
| if (count < 0) |
| return gtk_text_iter_backward_lines (iter, 0 - count); |
| else if (count == 0) |
| return FALSE; |
| else if (count == 1) |
| { |
| check_invariants (iter); |
| return gtk_text_iter_forward_line (iter); |
| } |
| else |
| { |
| gint old_line; |
| |
| if (gtk_text_iter_is_end (iter)) |
| return FALSE; |
| |
| old_line = gtk_text_iter_get_line (iter); |
| |
| gtk_text_iter_set_line (iter, old_line + count); |
| |
| if ((gtk_text_iter_get_line (iter) - old_line) < count) |
| { |
| /* count went past the last line, so move to end of last line */ |
| if (!gtk_text_iter_is_end (iter)) |
| gtk_text_iter_forward_to_end (iter); |
| } |
| |
| return !gtk_text_iter_is_end (iter); |
| } |
| } |
| |
| /** |
| * gtk_text_iter_backward_lines: |
| * @iter: a #GtkTextIter |
| * @count: number of lines to move backward |
| * |
| * Moves @count 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. |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_backward_lines (GtkTextIter *iter, gint count) |
| { |
| FIX_OVERFLOWS (count); |
| |
| if (count < 0) |
| return gtk_text_iter_forward_lines (iter, 0 - count); |
| else if (count == 0) |
| return FALSE; |
| else if (count == 1) |
| { |
| return gtk_text_iter_backward_line (iter); |
| } |
| else |
| { |
| gint old_line; |
| |
| old_line = gtk_text_iter_get_line (iter); |
| |
| gtk_text_iter_set_line (iter, MAX (old_line - count, 0)); |
| |
| return (gtk_text_iter_get_line (iter) != old_line); |
| } |
| } |
| |
| /** |
| * 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. |
| * |
| * Returns: whether @iter can be dereferenced |
| **/ |
| 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.) |
| * |
| * Returns: whether @iter moved |
| **/ |
| 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. |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| **/ |
| 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. |
| * |
| * Returns: whether @iter moved and is dereferenceable |
| **/ |
| 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 len, |
| gint *found_offset, |
| gboolean already_moved_initially); |
| |
| typedef gboolean (* TestLogAttrFunc) (const PangoLogAttr *attrs, |
| gint offset, |
| gint min_offset, |
| gint len); |
| |
| /* Word funcs */ |
| |
| static gboolean |
| find_word_end_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint len, |
| gint *found_offset, |
| gboolean already_moved_initially) |
| { |
| if (!already_moved_initially) |
| ++offset; |
| |
| /* Find end of next word */ |
| while (offset <= len) |
| { |
| if (attrs[offset].is_word_end) |
| { |
| *found_offset = offset; |
| return TRUE; |
| } |
| |
| ++offset; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| is_word_end_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint min_offset, |
| gint len) |
| { |
| return attrs[offset].is_word_end; |
| } |
| |
| static gboolean |
| find_word_start_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint len, |
| gint *found_offset, |
| gboolean already_moved_initially) |
| { |
| if (!already_moved_initially) |
| --offset; |
| |
| /* Find start of prev word */ |
| while (offset >= 0) |
| { |
| if (attrs[offset].is_word_start) |
| { |
| *found_offset = offset; |
| return TRUE; |
| } |
| |
| --offset; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| is_word_start_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint min_offset, |
| gint len) |
| { |
| return attrs[offset].is_word_start; |
| } |
| |
| static gboolean |
| inside_word_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint min_offset, |
| gint len) |
| { |
| /* Find next word start or end */ |
| while (offset >= min_offset && |
| !(attrs[offset].is_word_start || attrs[offset].is_word_end)) |
| --offset; |
| |
| if (offset >= 0) |
| return attrs[offset].is_word_start; |
| else |
| return FALSE; |
| } |
| |
| /* Sentence funcs */ |
| |
| static gboolean |
| find_sentence_end_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint len, |
| gint *found_offset, |
| gboolean already_moved_initially) |
| { |
| if (!already_moved_initially) |
| ++offset; |
| |
| /* Find end of next sentence */ |
| while (offset <= len) |
| { |
| if (attrs[offset].is_sentence_end) |
| { |
| *found_offset = offset; |
| return TRUE; |
| } |
| |
| ++offset; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| is_sentence_end_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint min_offset, |
| gint len) |
| { |
| return attrs[offset].is_sentence_end; |
| } |
| |
| static gboolean |
| find_sentence_start_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint len, |
| gint *found_offset, |
| gboolean already_moved_initially) |
| { |
| if (!already_moved_initially) |
| --offset; |
| |
| /* Find start of prev sentence */ |
| while (offset >= 0) |
| { |
| if (attrs[offset].is_sentence_start) |
| { |
| *found_offset = offset; |
| return TRUE; |
| } |
| |
| --offset; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| is_sentence_start_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint min_offset, |
| gint len) |
| { |
| return attrs[offset].is_sentence_start; |
| } |
| |
| static gboolean |
| inside_sentence_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint min_offset, |
| gint len) |
| { |
| /* Find next sentence start or end */ |
| while (!(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end)) |
| { |
| --offset; |
| if (offset < min_offset) |
| return FALSE; |
| } |
| |
| return attrs[offset].is_sentence_start; |
| } |
| |
| static gboolean |
| test_log_attrs (const GtkTextIter *iter, |
| TestLogAttrFunc func) |
| { |
| gint char_len; |
| const PangoLogAttr *attrs; |
| gint offset; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| attrs = _gtk_text_buffer_get_line_log_attrs (gtk_text_iter_get_buffer (iter), |
| iter, &char_len); |
| |
| offset = gtk_text_iter_get_line_offset (iter); |
| |
| g_assert (offset <= char_len); |
| |
| return (* func) (attrs, offset, 0, char_len); |
| } |
| |
| static gboolean |
| find_line_log_attrs (const GtkTextIter *iter, |
| FindLogAttrFunc func, |
| gint *found_offset, |
| gboolean already_moved_initially) |
| { |
| gint char_len; |
| const PangoLogAttr *attrs; |
| gint offset; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| attrs = _gtk_text_buffer_get_line_log_attrs (gtk_text_iter_get_buffer (iter), |
| iter, &char_len); |
| |
| offset = gtk_text_iter_get_line_offset (iter); |
| |
| return (* func) (attrs, |
| offset, |
| char_len, |
| found_offset, |
| already_moved_initially); |
| } |
| |
| static gboolean |
| find_by_log_attrs (GtkTextIter *arg_iter, |
| FindLogAttrFunc func, |
| gboolean forward) |
| { |
| GtkTextIter iter; |
| gboolean already_moved_initially = FALSE; |
| |
| g_return_val_if_fail (arg_iter != NULL, FALSE); |
| |
| iter = *arg_iter; |
| |
| while (TRUE) |
| { |
| gint offset = 0; |
| gboolean found; |
| |
| found = find_line_log_attrs (&iter, func, &offset, already_moved_initially); |
| |
| if (found) |
| { |
| gboolean moved; |
| |
| gtk_text_iter_set_line_offset (&iter, offset); |
| |
| moved = !gtk_text_iter_equal (&iter, arg_iter); |
| |
| *arg_iter = iter; |
| return moved && !gtk_text_iter_is_end (arg_iter); |
| } |
| |
| if (forward) |
| { |
| if (!gtk_text_iter_forward_line (&iter)) |
| return FALSE; |
| |
| already_moved_initially = TRUE; |
| } |
| else |
| { |
| /* Go to end of previous line. First go to the current line offset 0, |
| * because backward_line() snaps to start of line 0 if iter is already |
| * on line 0. |
| */ |
| gtk_text_iter_set_line_offset (&iter, 0); |
| |
| if (gtk_text_iter_backward_line (&iter)) |
| { |
| if (!gtk_text_iter_ends_line (&iter)) |
| gtk_text_iter_forward_to_line_end (&iter); |
| |
| already_moved_initially = TRUE; |
| } |
| else |
| { |
| return FALSE; |
| } |
| } |
| } |
| } |
| |
| static gboolean |
| find_visible_by_log_attrs (GtkTextIter *iter, |
| FindLogAttrFunc func, |
| gboolean forward) |
| { |
| GtkTextIter pos; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| pos = *iter; |
| |
| while (TRUE) |
| { |
| GtkTextIter pos_before = pos; |
| |
| find_by_log_attrs (&pos, func, forward); |
| |
| if (gtk_text_iter_equal (&pos_before, &pos)) |
| break; |
| |
| if (!_gtk_text_btree_char_is_invisible (&pos)) |
| { |
| *iter = pos; |
| return !gtk_text_iter_is_end (iter); |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| typedef gboolean (* OneStepFunc) (GtkTextIter *iter); |
| typedef gboolean (* MultipleStepFunc) (GtkTextIter *iter, gint count); |
| |
| static gboolean |
| move_multiple_steps (GtkTextIter *iter, |
| gint count, |
| OneStepFunc step_forward, |
| MultipleStepFunc n_steps_backward) |
| { |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| FIX_OVERFLOWS (count); |
| |
| if (count == 0) |
| return FALSE; |
| |
| if (count < 0) |
| return n_steps_backward (iter, -count); |
| |
| if (!step_forward (iter)) |
| return FALSE; |
| --count; |
| |
| while (count > 0) |
| { |
| if (!step_forward (iter)) |
| break; |
| --count; |
| } |
| |
| return !gtk_text_iter_is_end (iter); |
| } |
| |
| |
| /** |
| * gtk_text_iter_forward_word_end: |
| * @iter: a #GtkTextIter |
| * |
| * Moves forward to the next word end. (If @iter is currently on a |
| * word end, moves forward to the next one after that.) Word breaks |
| * are determined by Pango and should be correct for nearly any |
| * language (if not, the correct fix would be to the Pango word break |
| * algorithms). |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_forward_word_end (GtkTextIter *iter) |
| { |
| return find_by_log_attrs (iter, find_word_end_func, TRUE); |
| } |
| |
| /** |
| * gtk_text_iter_backward_word_start: |
| * @iter: a #GtkTextIter |
| * |
| * Moves backward to the previous word start. (If @iter is currently on a |
| * word start, moves backward to the next one after that.) Word breaks |
| * are determined by Pango and should be correct for nearly any |
| * language (if not, the correct fix would be to the Pango word break |
| * algorithms). |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_backward_word_start (GtkTextIter *iter) |
| { |
| return find_by_log_attrs (iter, find_word_start_func, FALSE); |
| } |
| |
| /* FIXME a loop around a truly slow function means |
| * a truly spectacularly slow function. |
| */ |
| |
| /** |
| * gtk_text_iter_forward_word_ends: |
| * @iter: a #GtkTextIter |
| * @count: number of times to move |
| * |
| * Calls gtk_text_iter_forward_word_end() up to @count times. |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_forward_word_ends (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_forward_word_end, |
| gtk_text_iter_backward_word_starts); |
| } |
| |
| /** |
| * gtk_text_iter_backward_word_starts: |
| * @iter: a #GtkTextIter |
| * @count: number of times to move |
| * |
| * Calls gtk_text_iter_backward_word_start() up to @count times. |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_backward_word_starts (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_backward_word_start, |
| gtk_text_iter_forward_word_ends); |
| } |
| |
| /** |
| * gtk_text_iter_forward_visible_word_end: |
| * @iter: a #GtkTextIter |
| * |
| * Moves forward to the next visible word end. (If @iter is currently on a |
| * word end, moves forward to the next one after that.) Word breaks |
| * are determined by Pango and should be correct for nearly any |
| * language (if not, the correct fix would be to the Pango word break |
| * algorithms). |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_forward_visible_word_end (GtkTextIter *iter) |
| { |
| return find_visible_by_log_attrs (iter, find_word_end_func, TRUE); |
| } |
| |
| /** |
| * gtk_text_iter_backward_visible_word_start: |
| * @iter: a #GtkTextIter |
| * |
| * Moves backward to the previous visible word start. (If @iter is currently |
| * on a word start, moves backward to the next one after that.) Word breaks |
| * are determined by Pango and should be correct for nearly any |
| * language (if not, the correct fix would be to the Pango word break |
| * algorithms). |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_backward_visible_word_start (GtkTextIter *iter) |
| { |
| return find_visible_by_log_attrs (iter, find_word_start_func, FALSE); |
| } |
| |
| /** |
| * gtk_text_iter_forward_visible_word_ends: |
| * @iter: a #GtkTextIter |
| * @count: number of times to move |
| * |
| * Calls gtk_text_iter_forward_visible_word_end() up to @count times. |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_forward_visible_word_ends (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_forward_visible_word_end, |
| gtk_text_iter_backward_visible_word_starts); |
| } |
| |
| /** |
| * gtk_text_iter_backward_visible_word_starts: |
| * @iter: a #GtkTextIter |
| * @count: number of times to move |
| * |
| * Calls gtk_text_iter_backward_visible_word_start() up to @count times. |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_backward_visible_word_starts (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_backward_visible_word_start, |
| gtk_text_iter_forward_visible_word_ends); |
| } |
| |
| /** |
| * gtk_text_iter_starts_word: |
| * @iter: a #GtkTextIter |
| * |
| * Determines whether @iter begins a natural-language word. Word |
| * breaks are determined by Pango and should be correct for nearly any |
| * language (if not, the correct fix would be to the Pango word break |
| * algorithms). |
| * |
| * Returns: %TRUE if @iter is at the start of a word |
| **/ |
| gboolean |
| gtk_text_iter_starts_word (const GtkTextIter *iter) |
| { |
| return test_log_attrs (iter, is_word_start_func); |
| } |
| |
| /** |
| * gtk_text_iter_ends_word: |
| * @iter: a #GtkTextIter |
| * |
| * Determines whether @iter ends a natural-language word. Word breaks |
| * are determined by Pango and should be correct for nearly any |
| * language (if not, the correct fix would be to the Pango word break |
| * algorithms). |
| * |
| * Returns: %TRUE if @iter is at the end of a word |
| **/ |
| gboolean |
| gtk_text_iter_ends_word (const GtkTextIter *iter) |
| { |
| return test_log_attrs (iter, is_word_end_func); |
| } |
| |
| /** |
| * gtk_text_iter_inside_word: |
| * @iter: a #GtkTextIter |
| * |
| * Determines whether the character pointed by @iter is part of a |
| * natural-language word (as opposed to say inside some whitespace). Word |
| * breaks are determined by Pango and should be correct for nearly any language |
| * (if not, the correct fix would be to the Pango word break algorithms). |
| * |
| * Note that if gtk_text_iter_starts_word() returns %TRUE, then this function |
| * returns %TRUE too, since @iter points to the first character of the word. |
| * |
| * Returns: %TRUE if @iter is inside a word |
| **/ |
| gboolean |
| gtk_text_iter_inside_word (const GtkTextIter *iter) |
| { |
| return test_log_attrs (iter, inside_word_func); |
| } |
| |
| /** |
| * gtk_text_iter_starts_sentence: |
| * @iter: a #GtkTextIter |
| * |
| * Determines whether @iter begins a sentence. Sentence boundaries are |
| * determined by Pango and should be correct for nearly any language |
| * (if not, the correct fix would be to the Pango text boundary |
| * algorithms). |
| * |
| * Returns: %TRUE if @iter is at the start of a sentence. |
| **/ |
| gboolean |
| gtk_text_iter_starts_sentence (const GtkTextIter *iter) |
| { |
| return test_log_attrs (iter, is_sentence_start_func); |
| } |
| |
| /** |
| * gtk_text_iter_ends_sentence: |
| * @iter: a #GtkTextIter |
| * |
| * Determines whether @iter ends a sentence. Sentence boundaries are |
| * determined by Pango and should be correct for nearly any language |
| * (if not, the correct fix would be to the Pango text boundary |
| * algorithms). |
| * |
| * Returns: %TRUE if @iter is at the end of a sentence. |
| **/ |
| gboolean |
| gtk_text_iter_ends_sentence (const GtkTextIter *iter) |
| { |
| return test_log_attrs (iter, is_sentence_end_func); |
| } |
| |
| /** |
| * gtk_text_iter_inside_sentence: |
| * @iter: a #GtkTextIter |
| * |
| * Determines whether @iter is inside a sentence (as opposed to in |
| * between two sentences, e.g. after a period and before the first |
| * letter of the next sentence). Sentence boundaries are determined |
| * by Pango and should be correct for nearly any language (if not, the |
| * correct fix would be to the Pango text boundary algorithms). |
| * |
| * Returns: %TRUE if @iter is inside a sentence. |
| **/ |
| gboolean |
| gtk_text_iter_inside_sentence (const GtkTextIter *iter) |
| { |
| return test_log_attrs (iter, inside_sentence_func); |
| } |
| |
| /** |
| * gtk_text_iter_forward_sentence_end: |
| * @iter: a #GtkTextIter |
| * |
| * Moves forward to the next sentence end. (If @iter is at the end of |
| * a sentence, moves to the next end of sentence.) Sentence |
| * boundaries are determined by Pango and should be correct for nearly |
| * any language (if not, the correct fix would be to the Pango text |
| * boundary algorithms). |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_forward_sentence_end (GtkTextIter *iter) |
| { |
| return find_by_log_attrs (iter, find_sentence_end_func, TRUE); |
| } |
| |
| /** |
| * gtk_text_iter_backward_sentence_start: |
| * @iter: a #GtkTextIter |
| * |
| * Moves backward to the previous sentence start; if @iter is already at |
| * the start of a sentence, moves backward to the next one. Sentence |
| * boundaries are determined by Pango and should be correct for nearly |
| * any language (if not, the correct fix would be to the Pango text |
| * boundary algorithms). |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_backward_sentence_start (GtkTextIter *iter) |
| { |
| return find_by_log_attrs (iter, find_sentence_start_func, FALSE); |
| } |
| |
| /* FIXME a loop around a truly slow function means |
| * a truly spectacularly slow function. |
| */ |
| /** |
| * gtk_text_iter_forward_sentence_ends: |
| * @iter: a #GtkTextIter |
| * @count: number of sentences to move |
| * |
| * Calls gtk_text_iter_forward_sentence_end() @count times (or until |
| * gtk_text_iter_forward_sentence_end() returns %FALSE). If @count is |
| * negative, moves backward instead of forward. |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_forward_sentence_ends (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_forward_sentence_end, |
| gtk_text_iter_backward_sentence_starts); |
| } |
| |
| /** |
| * gtk_text_iter_backward_sentence_starts: |
| * @iter: a #GtkTextIter |
| * @count: number of sentences to move |
| * |
| * Calls gtk_text_iter_backward_sentence_start() up to @count times, |
| * or until it returns %FALSE. If @count is negative, moves forward |
| * instead of backward. |
| * |
| * Returns: %TRUE if @iter moved and is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_backward_sentence_starts (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_backward_sentence_start, |
| gtk_text_iter_forward_sentence_ends); |
| } |
| |
| static gboolean |
| find_forward_cursor_pos_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint len, |
| gint *found_offset, |
| gboolean already_moved_initially) |
| { |
| if (!already_moved_initially) |
| ++offset; |
| |
| while (offset <= len) |
| { |
| if (attrs[offset].is_cursor_position) |
| { |
| *found_offset = offset; |
| return TRUE; |
| } |
| |
| ++offset; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| find_backward_cursor_pos_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint len, |
| gint *found_offset, |
| gboolean already_moved_initially) |
| { |
| if (!already_moved_initially) |
| --offset; |
| |
| while (offset >= 0) |
| { |
| if (attrs[offset].is_cursor_position) |
| { |
| *found_offset = offset; |
| return TRUE; |
| } |
| |
| --offset; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| is_cursor_pos_func (const PangoLogAttr *attrs, |
| gint offset, |
| gint min_offset, |
| gint len) |
| { |
| return attrs[offset].is_cursor_position; |
| } |
| |
| /** |
| * gtk_text_iter_forward_cursor_position: |
| * @iter: a #GtkTextIter |
| * |
| * Moves @iter forward by a single cursor position. Cursor positions |
| * are (unsurprisingly) positions where the cursor can appear. Perhaps |
| * surprisingly, there may not be a cursor position between all |
| * characters. The most common example for European languages would be |
| * a carriage return/newline sequence. For some Unicode characters, |
| * the equivalent of say the letter “a” with an accent mark will be |
| * represented as two characters, first the letter then a "combining |
| * mark" that causes the accent to be rendered; so the cursor can’t go |
| * between those two characters. See also the #PangoLogAttr-struct and |
| * pango_break() function. |
| * |
| * Returns: %TRUE if we moved and the new position is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_forward_cursor_position (GtkTextIter *iter) |
| { |
| return find_by_log_attrs (iter, find_forward_cursor_pos_func, TRUE); |
| } |
| |
| /** |
| * gtk_text_iter_backward_cursor_position: |
| * @iter: a #GtkTextIter |
| * |
| * Like gtk_text_iter_forward_cursor_position(), but moves backward. |
| * |
| * Returns: %TRUE if we moved |
| **/ |
| gboolean |
| gtk_text_iter_backward_cursor_position (GtkTextIter *iter) |
| { |
| return find_by_log_attrs (iter, find_backward_cursor_pos_func, FALSE); |
| } |
| |
| /** |
| * gtk_text_iter_forward_cursor_positions: |
| * @iter: a #GtkTextIter |
| * @count: number of positions to move |
| * |
| * Moves up to @count cursor positions. See |
| * gtk_text_iter_forward_cursor_position() for details. |
| * |
| * Returns: %TRUE if we moved and the new position is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_forward_cursor_positions (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_forward_cursor_position, |
| gtk_text_iter_backward_cursor_positions); |
| } |
| |
| /** |
| * gtk_text_iter_backward_cursor_positions: |
| * @iter: a #GtkTextIter |
| * @count: number of positions to move |
| * |
| * Moves up to @count cursor positions. See |
| * gtk_text_iter_forward_cursor_position() for details. |
| * |
| * Returns: %TRUE if we moved and the new position is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_backward_cursor_positions (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_backward_cursor_position, |
| gtk_text_iter_forward_cursor_positions); |
| } |
| |
| /** |
| * gtk_text_iter_forward_visible_cursor_position: |
| * @iter: a #GtkTextIter |
| * |
| * Moves @iter forward to the next visible cursor position. See |
| * gtk_text_iter_forward_cursor_position() for details. |
| * |
| * Returns: %TRUE if we moved and the new position is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_forward_visible_cursor_position (GtkTextIter *iter) |
| { |
| return find_visible_by_log_attrs (iter, find_forward_cursor_pos_func, TRUE); |
| } |
| |
| /** |
| * gtk_text_iter_backward_visible_cursor_position: |
| * @iter: a #GtkTextIter |
| * |
| * Moves @iter forward to the previous visible cursor position. See |
| * gtk_text_iter_backward_cursor_position() for details. |
| * |
| * Returns: %TRUE if we moved and the new position is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_backward_visible_cursor_position (GtkTextIter *iter) |
| { |
| return find_visible_by_log_attrs (iter, find_backward_cursor_pos_func, FALSE); |
| } |
| |
| /** |
| * gtk_text_iter_forward_visible_cursor_positions: |
| * @iter: a #GtkTextIter |
| * @count: number of positions to move |
| * |
| * Moves up to @count visible cursor positions. See |
| * gtk_text_iter_forward_cursor_position() for details. |
| * |
| * Returns: %TRUE if we moved and the new position is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_forward_visible_cursor_positions (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_forward_visible_cursor_position, |
| gtk_text_iter_backward_visible_cursor_positions); |
| } |
| |
| /** |
| * gtk_text_iter_backward_visible_cursor_positions: |
| * @iter: a #GtkTextIter |
| * @count: number of positions to move |
| * |
| * Moves up to @count visible cursor positions. See |
| * gtk_text_iter_backward_cursor_position() for details. |
| * |
| * Returns: %TRUE if we moved and the new position is dereferenceable |
| **/ |
| gboolean |
| gtk_text_iter_backward_visible_cursor_positions (GtkTextIter *iter, |
| gint count) |
| { |
| return move_multiple_steps (iter, count, |
| gtk_text_iter_backward_visible_cursor_position, |
| gtk_text_iter_forward_visible_cursor_positions); |
| } |
| |
| /** |
| * gtk_text_iter_is_cursor_position: |
| * @iter: a #GtkTextIter |
| * |
| * See gtk_text_iter_forward_cursor_position() or #PangoLogAttr or |
| * pango_break() for details on what a cursor position is. |
| * |
| * Returns: %TRUE if the cursor can be placed at @iter |
| **/ |
| gboolean |
| gtk_text_iter_is_cursor_position (const GtkTextIter *iter) |
| { |
| return test_log_attrs (iter, is_cursor_pos_func); |
| } |
| |
| /** |
| * gtk_text_iter_set_line_offset: |
| * @iter: a #GtkTextIter |
| * @char_on_line: a character offset relative to the start of @iter’s current line |
| * |
| * Moves @iter within a line, to a new character |
| * (not byte) offset. The given character offset must be less than or |
| * equal to the number of characters in the line; if equal, @iter |
| * moves to the start of the next line. See |
| * gtk_text_iter_set_line_index() if you have a byte index rather than |
| * a character offset. |
| * |
| **/ |
| void |
| gtk_text_iter_set_line_offset (GtkTextIter *iter, |
| gint char_on_line) |
| { |
| GtkTextRealIter *real; |
| gint chars_in_line; |
| |
| g_return_if_fail (iter != NULL); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return; |
| |
| check_invariants (iter); |
| |
| chars_in_line = gtk_text_iter_get_chars_in_line (iter); |
| |
| g_return_if_fail (char_on_line <= chars_in_line); |
| |
| if (char_on_line < chars_in_line) |
| iter_set_from_char_offset (real, real->line, char_on_line); |
| else |
| gtk_text_iter_forward_line (iter); /* set to start of next line */ |
| |
| check_invariants (iter); |
| } |
| |
| /** |
| * gtk_text_iter_set_line_index: |
| * @iter: a #GtkTextIter |
| * @byte_on_line: a byte index relative to the start of @iter’s current line |
| * |
| * Same as gtk_text_iter_set_line_offset(), but works with a |
| * byte index. The given byte index must be at |
| * the start of a character, it can’t be in the middle of a UTF-8 |
| * encoded character. |
| * |
| **/ |
| void |
| gtk_text_iter_set_line_index (GtkTextIter *iter, |
| gint byte_on_line) |
| { |
| GtkTextRealIter *real; |
| gint bytes_in_line; |
| |
| g_return_if_fail (iter != NULL); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return; |
| |
| check_invariants (iter); |
| |
| bytes_in_line = gtk_text_iter_get_bytes_in_line (iter); |
| |
| g_return_if_fail (byte_on_line <= bytes_in_line); |
| |
| if (byte_on_line < bytes_in_line) |
| iter_set_from_byte_offset (real, real->line, byte_on_line); |
| else |
| gtk_text_iter_forward_line (iter); |
| |
| if (real->segment->type == >k_text_char_type && |
| (real->segment->body.chars[real->segment_byte_offset] & 0xc0) == 0x80) |
| g_warning ("%s: Incorrect byte offset %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); |
| |
| check_invariants (iter); |
| } |
| |
| |
| /** |
| * gtk_text_iter_set_visible_line_offset: |
| * @iter: a #GtkTextIter |
| * @char_on_line: a character offset |
| * |
| * Like gtk_text_iter_set_line_offset(), but the offset is in visible |
| * characters, i.e. text with a tag making it invisible is not |
| * counted in the offset. |
| **/ |
| void |
| gtk_text_iter_set_visible_line_offset (GtkTextIter *iter, |
| gint char_on_line) |
| { |
| gint chars_seen = 0; |
| GtkTextIter pos; |
| |
| 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 (chars_seen < char_on_line) |
| { |
| if (!_gtk_text_btree_char_is_invisible (&pos)) |
| ++chars_seen; |
| |
| if (!gtk_text_iter_forward_char (&pos)) |
| break; |
| |
| if (chars_seen == char_on_line) |
| break; |
| } |
| |
| if (_gtk_text_iter_get_text_line (&pos) == _gtk_text_iter_get_text_line (iter)) |
| *iter = pos; |
| else |
| gtk_text_iter_forward_line (iter); |
| } |
| |
| /** |
| * gtk_text_iter_set_visible_line_index: |
| * @iter: a #GtkTextIter |
| * @byte_on_line: a byte index |
| * |
| * Like gtk_text_iter_set_line_index(), but the index is in visible |
| * bytes, i.e. text with a tag making it invisible is not counted |
| * in the index. |
| **/ |
| void |
| gtk_text_iter_set_visible_line_index (GtkTextIter *iter, |
| gint byte_on_line) |
| { |
| GtkTextRealIter *real; |
| gint offset = 0; |
| GtkTextIter pos; |
| GtkTextLineSegment *seg; |
| |
| g_return_if_fail (iter != NULL); |
| |
| gtk_text_iter_set_line_offset (iter, 0); |
| |
| pos = *iter; |
| |
| 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)) |
| { |
| 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; |
| } |
| |
| offset += seg->byte_count; |
| _gtk_text_iter_forward_indexable_segment (&pos); |
| seg = _gtk_text_iter_get_indexable_segment (&pos); |
| } |
| |
| if (byte_on_line == 0) |
| *iter = pos; |
| else |
| gtk_text_iter_forward_line (iter); |
| } |
| |
| /** |
| * gtk_text_iter_set_line: |
| * @iter: a #GtkTextIter |
| * @line_number: line number (counted from 0) |
| * |
| * Moves iterator @iter to the start of the line @line_number. If |
| * @line_number is negative or larger than the number of lines in the |
| * buffer, moves @iter to the start of the last line in the buffer. |
| * |
| **/ |
| void |
| gtk_text_iter_set_line (GtkTextIter *iter, |
| gint line_number) |
| { |
| GtkTextLine *line; |
| gint real_line; |
| GtkTextRealIter *real; |
| |
| g_return_if_fail (iter != NULL); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return; |
| |
| check_invariants (iter); |
| |
| line = _gtk_text_btree_get_line_no_last (real->tree, line_number, &real_line); |
| |
| iter_set_from_char_offset (real, line, 0); |
| |
| /* We might as well cache this, since we know it. */ |
| real->cached_line_number = real_line; |
| |
| check_invariants (iter); |
| } |
| |
| /** |
| * gtk_text_iter_set_offset: |
| * @iter: a #GtkTextIter |
| * @char_offset: a character number |
| * |
| * Sets @iter to point to @char_offset. @char_offset counts from the start |
| * of the entire text buffer, starting with 0. |
| **/ |
| void |
| gtk_text_iter_set_offset (GtkTextIter *iter, |
| gint char_offset) |
| { |
| GtkTextLine *line; |
| GtkTextRealIter *real; |
| gint line_start; |
| gint real_char_index; |
| |
| g_return_if_fail (iter != NULL); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return; |
| |
| check_invariants (iter); |
| |
| if (real->cached_char_index >= 0 && |
| real->cached_char_index == char_offset) |
| return; |
| |
| line = _gtk_text_btree_get_line_at_char (real->tree, |
| char_offset, |
| &line_start, |
| &real_char_index); |
| |
| iter_set_from_char_offset (real, line, real_char_index - line_start); |
| |
| /* Go ahead and cache this since we have it. */ |
| real->cached_char_index = real_char_index; |
| |
| check_invariants (iter); |
| } |
| |
| /** |
| * gtk_text_iter_forward_to_end: |
| * @iter: a #GtkTextIter |
| * |
| * Moves @iter forward to the “end iterator,” which points one past the last |
| * valid character in the buffer. gtk_text_iter_get_char() called on the |
| * end iterator returns 0, which is convenient for writing loops. |
| **/ |
| void |
| gtk_text_iter_forward_to_end (GtkTextIter *iter) |
| { |
| GtkTextBuffer *buffer; |
| GtkTextRealIter *real; |
| |
| g_return_if_fail (iter != NULL); |
| |
| real = gtk_text_iter_make_surreal (iter); |
| |
| if (real == NULL) |
| return; |
| |
| buffer = _gtk_text_btree_get_buffer (real->tree); |
| |
| gtk_text_buffer_get_end_iter (buffer, iter); |
| } |
| |
| /* FIXME this and gtk_text_iter_forward_to_line_end() could be cleaned up |
| * and made faster. Look at iter_ends_line() for inspiration, perhaps. |
| * If all else fails we could cache the para delimiter pos in the iter. |
| * I think forward_to_line_end() actually gets called fairly often. |
| */ |
| static int |
| find_paragraph_delimiter_for_line (GtkTextIter *iter) |
| { |
| GtkTextIter end; |
| end = *iter; |
| |
| if (_gtk_text_line_contains_end_iter (_gtk_text_iter_get_text_line (&end), |
| _gtk_text_iter_get_btree (&end))) |
| { |
| gtk_text_iter_forward_to_end (&end); |
| } |
| else |
| { |
| /* if we aren't on the last line, go forward to start of next line, then scan |
| * back for the delimiters on the previous line |
| */ |
| gtk_text_iter_forward_line (&end); |
| gtk_text_iter_backward_char (&end); |
| while (!gtk_text_iter_ends_line (&end)) |
| gtk_text_iter_backward_char (&end); |
| } |
| |
| return gtk_text_iter_get_line_offset (&end); |
| } |
| |
| /** |
| * gtk_text_iter_forward_to_line_end: |
| * @iter: a #GtkTextIter |
| * |
| * Moves the iterator to point to the paragraph delimiter characters, |
| * which will be either a newline, a carriage return, a carriage |
| * return/newline in sequence, or the Unicode paragraph separator |
| * character. If the iterator is already at the paragraph delimiter |
| * characters, moves to the paragraph delimiter characters for the |
| * next line. If @iter is on the last line in the buffer, which does |
| * not end in paragraph delimiters, moves to the end iterator (end of |
| * the last line), and returns %FALSE. |
| * |
| * Returns: %TRUE if we moved and the new location is not the end iterator |
| **/ |
| gboolean |
| gtk_text_iter_forward_to_line_end (GtkTextIter *iter) |
| { |
| gint current_offset; |
| gint new_offset; |
| |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| current_offset = gtk_text_iter_get_line_offset (iter); |
| new_offset = find_paragraph_delimiter_for_line (iter); |
| |
| if (current_offset < new_offset) |
| { |
| /* Move to end of this line. */ |
| gtk_text_iter_set_line_offset (iter, new_offset); |
| return !gtk_text_iter_is_end (iter); |
| } |
| else |
| { |
| /* Move to end of next line. */ |
| if (gtk_text_iter_forward_line (iter)) |
| { |
| /* We don't want to move past all |
| * empty lines. |
| */ |
| if (!gtk_text_iter_ends_line (iter)) |
| gtk_text_iter_forward_to_line_end (iter); |
| return !gtk_text_iter_is_end (iter); |
| } |
| else |
| return FALSE; |
| } |
| } |
| |
| /** |
| * gtk_text_iter_forward_to_tag_toggle: |
| * @iter: a #GtkTextIter |
| * @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 |
| * @tag is %NULL. If no matching tag toggles are found, |
| * returns %FALSE, otherwise %TRUE. Does not return toggles |
| * located at @iter, only toggles after @iter. Sets @iter to |
| * the location of the toggle, or to the end of the buffer |
| * if no toggle is found. |
| * |
| * Returns: whether we found a tag toggle after @iter |
| **/ |
| gboolean |
| gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter, |
| GtkTextTag *tag) |
| { |
| GtkTextLine *next_line; |
| GtkTextLine *current_line; |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| if (gtk_text_iter_is_end (iter)) |
| return FALSE; |
| |
| current_line = real->line; |
| next_line = _gtk_text_line_next_could_contain_tag (current_line, |
| real->tree, tag); |
| |
| while (_gtk_text_iter_forward_indexable_segment (iter)) |
| { |
| /* If we went forward to a line that couldn't contain a toggle |
| for the tag, then skip forward to a line that could contain |
| it. This potentially skips huge hunks of the tree, so we |
| aren't a purely linear search. */ |
| if (real->line != current_line) |
| { |
| if (next_line == NULL) |
| { |
| /* End of search. Set to end of buffer. */ |
| _gtk_text_btree_get_end_iter (real->tree, iter); |
| return FALSE; |
| } |
| |
| if (real->line != next_line) |
| iter_set_from_byte_offset (real, next_line, 0); |
| |
| current_line = real->line; |
| next_line = _gtk_text_line_next_could_contain_tag (current_line, |
| real->tree, |
| tag); |
| } |
| |
| if (gtk_text_iter_toggles_tag (iter, tag)) |
| { |
| /* If there's a toggle here, it isn't indexable so |
| any_segment can't be the indexable segment. */ |
| g_assert (real->any_segment != real->segment); |
| return TRUE; |
| } |
| } |
| |
| /* Check end iterator for tags */ |
| if (gtk_text_iter_toggles_tag (iter, tag)) |
| { |
| /* If there's a toggle here, it isn't indexable so |
| any_segment can't be the indexable segment. */ |
| g_assert (real->any_segment != real->segment); |
| return TRUE; |
| } |
| |
| /* Reached end of buffer */ |
| return FALSE; |
| } |
| |
| /** |
| * gtk_text_iter_backward_to_tag_toggle: |
| * @iter: a #GtkTextIter |
| * @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 |
| * @tag is %NULL. If no matching tag toggles are found, |
| * returns %FALSE, otherwise %TRUE. Does not return toggles |
| * located at @iter, only toggles before @iter. Sets @iter |
| * to the location of the toggle, or the start of the buffer |
| * if no toggle is found. |
| * |
| * Returns: whether we found a tag toggle before @iter |
| **/ |
| gboolean |
| gtk_text_iter_backward_to_tag_toggle (GtkTextIter *iter, |
| GtkTextTag *tag) |
| { |
| GtkTextLine *prev_line; |
| GtkTextLine *current_line; |
| GtkTextRealIter *real; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| |
| real = gtk_text_iter_make_real (iter); |
| |
| if (real == NULL) |
| return FALSE; |
| |
| check_invariants (iter); |
| |
| current_line = real->line; |
| prev_line = _gtk_text_line_previous_could_contain_tag (current_line, |
| real->tree, tag); |
| |
| |
| /* If we're at segment start, go to the previous segment; |
| * if mid-segment, snap to start of current segment. |
| */ |
| if (is_segment_start (real)) |
| { |
| if (!_gtk_text_iter_backward_indexable_segment (iter)) |
| return FALSE; |
| } |
| else |
| { |
| ensure_char_offsets (real); |
| |
| if (!gtk_text_iter_backward_chars (iter, real->segment_char_offset)) |
| return FALSE; |
| } |
| |
| do |
| { |
| /* If we went backward to a line that couldn't contain a toggle |
| * for the tag, then skip backward further to a line that |
| * could contain it. This potentially skips huge hunks of the |
| * tree, so we aren't a purely linear search. |
| */ |
| if (real->line != current_line) |
| { |
| if (prev_line == NULL) |
| { |
| /* End of search. Set to start of buffer. */ |
| _gtk_text_btree_get_iter_at_char (real->tree, iter, 0); |
| return FALSE; |
| } |
| |
| if (real->line != prev_line) |
| { |
| /* Set to last segment in prev_line (could do this |
| * more quickly) |
| */ |
| iter_set_from_byte_offset (real, prev_line, 0); |
| |
| while (!at_last_indexable_segment (real)) |
| _gtk_text_iter_forward_indexable_segment (iter); |
| } |
| |
| current_line = real->line; |
| prev_line = _gtk_text_line_previous_could_contain_tag (current_line, |
| real->tree, |
| tag); |
| } |
| |
| if (gtk_text_iter_toggles_tag (iter, tag)) |
| { |
| /* If there's a toggle here, it isn't indexable so |
| * any_segment can't be the indexable segment. |
| */ |
| g_assert (real->any_segment != real->segment); |
| return TRUE; |
| } |
| } |
| while (_gtk_text_iter_backward_indexable_segment (iter)); |
| |
| /* Reached front of buffer */ |
| return FALSE; |
| } |
| |
| static gboolean |
| matches_pred (GtkTextIter *iter, |
| GtkTextCharPredicate pred, |
| gpointer user_data) |
| { |
| gint ch; |
| |
| ch = gtk_text_iter_get_char (iter); |
| |
| return (*pred) (ch, user_data); |
| } |
| |
| /** |
| * gtk_text_iter_forward_find_char: |
| * @iter: a #GtkTextIter |
| * @pred: (scope call): a function to be called on each character |
| * @user_data: user data for @pred |
| * @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. |
| * If @pred never returns %TRUE, @iter is set to @limit if |
| * @limit is non-%NULL, otherwise to the end iterator. |
| * |
| * Returns: whether a match was found |
| **/ |
| gboolean |
| gtk_text_iter_forward_find_char (GtkTextIter *iter, |
| GtkTextCharPredicate pred, |
| gpointer user_data, |
| const GtkTextIter *limit) |
| { |
| g_return_val_if_fail (iter != NULL, FALSE); |
| g_return_val_if_fail (pred != NULL, FALSE); |
| |
| if (limit && |
| gtk_text_iter_compare (iter, limit) >= 0) |
| return FALSE; |
| |
| while ((limit == NULL || |
| !gtk_text_iter_equal (limit, iter)) && |
| gtk_text_iter_forward_char (iter)) |
| { |
| if (matches_pred (iter, pred, user_data)) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| /** |
| * gtk_text_iter_backward_find_char: |
| * @iter: a #GtkTextIter |
| * @pred: (scope call): function to be called on each character |
| * @user_data: user data for @pred |
| * @limit: (allow-none): search limit, or %NULL for none |
| * |
| * Same as gtk_text_iter_forward_find_char(), but goes backward from @iter. |
| * |
| * Returns: whether a match was found |
| **/ |
| gboolean |
| gtk_text_iter_backward_find_char (GtkTextIter *iter, |
| GtkTextCharPredicate pred, |
| gpointer user_data, |
| const GtkTextIter *limit) |
| { |
| g_return_val_if_fail (iter != NULL, FALSE); |
| g_return_val_if_fail (pred != NULL, FALSE); |
| |
| if (limit && |
| gtk_text_iter_compare (iter, limit) <= 0) |
| return FALSE; |
| |
| while ((limit == NULL || |
| !gtk_text_iter_equal (limit, iter)) && |
| gtk_text_iter_backward_char (iter)) |
| { |
| if (matches_pred (iter, pred, user_data)) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static void |
| forward_chars_with_skipping (GtkTextIter *iter, |
| gint count, |
| gboolean skip_invisible, |
| gboolean skip_nontext, |
| gboolean skip_decomp) |
| { |
| gint i; |
| |
| g_return_if_fail (count >= 0); |
| |
| i = count; |
| |
| while (i > 0) |
| { |
| 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; |
| |
| if (!ignored && |
| skip_invisible && |
| _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) |
| --i; |
| } |
| } |
| |
| 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 (TRUE) |
| { |
| if (exact_prefix_cmp (p, needle, needle_len)) |
| { |
| ret = pointer_from_offset_skipping_decomp (haystack, i); |
| break; |
| } |
| |
| if (p == caseless_haystack) |
| break; |
| |
| 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) |
| { |
| GtkTextIter next; |
| gchar *line_text; |
| const gchar *found; |
| gint offset; |
| |
| if (*lines == NULL || **lines == '\0') |
| { |
| if (match_start) |
| *match_start = *start; |
| |
| if (match_end) |
| *match_end = *start; |
| return TRUE; |
| } |
| |
| next = *start; |
| gtk_text_iter_forward_line (&next); |
| |
| /* No more text in buffer, but *lines is nonempty */ |
| if (gtk_text_iter_equal (start, &next)) |
| { |
| return FALSE; |
| } |
| |
| if (slice) |
| { |
| if (visible_only) |
| line_text = gtk_text_iter_get_visible_slice (start, &next); |
| else |
| line_text = gtk_text_iter_get_slice (start, &next); |
| } |
| else |
| { |
| if (visible_only) |
| line_text = gtk_text_iter_get_visible_text (start, &next); |
| else |
| line_text = gtk_text_iter_get_text (start, &next); |
| } |
| |
| if (match_start) /* if this is the first line we're matching */ |
| { |
| 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 ((!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; |
| } |
| |
| if (found == NULL) |
| { |
| g_free (line_text); |
| return FALSE; |
| } |
| |
| /* Get offset to start of search string */ |
| offset = g_utf8_strlen (line_text, found - line_text); |
| |
| next = *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; |
| |
| /* Go to end of search string */ |
| forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), |
| visible_only, !slice, case_insensitive); |
| |
| g_free (line_text); |
| |
| ++lines; |
| |
| if (match_end) |
| *match_end = next; |
| |
| /* pass NULL for match_start, since we don't need to find the |
| * start again. |
| */ |
| 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 *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); |
| g_return_val_if_fail (delimiter != NULL, NULL); |
| |
| if (max_tokens < 1) |
| max_tokens = G_MAXINT; |
| |
| s = strstr (string, delimiter); |
| if (s) |
| { |
| guint delimiter_len = strlen (delimiter); |
| |
| do |
| { |
| guint len; |
| |
| 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; |
| s = strstr (string, delimiter); |
| } |
| while (--max_tokens && s); |
| } |
| if (*string) |
| { |
| n++; |
| |
| 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); |
| |
| i = n - 1; |
| |
| str_array[i--] = NULL; |
| for (slist = string_list; slist; slist = slist->next) |
| str_array[i--] = slist->data; |
| |
| g_slist_free (string_list); |
| |
| if (num_strings != NULL) |
| *num_strings = n - 1; |
| |
| return str_array; |
| } |
| |
| /** |
| * gtk_text_iter_forward_search: |
| * @iter: start of search |
| * @str: a search string |
| * @flags: flags affecting how the search is done |
| * @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_end, 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 |
| * buffers. |
| * |
| * @match_start will never be set to a #GtkTextIter located before @iter, even if |
| * there is a possible @match_end after or at @iter. |
| * |
| * Returns: whether a match was found |
| **/ |
| gboolean |
| gtk_text_iter_forward_search (const GtkTextIter *iter, |
| const gchar *str, |
| GtkTextSearchFlags flags, |
| GtkTextIter *match_start, |
| GtkTextIter *match_end, |
| const GtkTextIter *limit) |
| { |
| gchar **lines = NULL; |
| GtkTextIter match; |
| gboolean retval = FALSE; |
| 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); |
| |
| if (limit && |
| gtk_text_iter_compare (iter, limit) >= 0) |
| return FALSE; |
| |
| if (*str == '\0') |
| { |
| /* If we can move one char, return the empty string there */ |
| match = *iter; |
| |
| if (gtk_text_iter_forward_char (&match)) |
| { |
| if (limit && |
| gtk_text_iter_equal (&match, limit)) |
| return FALSE; |
| |
| if (match_start) |
| *match_start = match; |
| if (match_end) |
| *match_end = match; |
| return TRUE; |
| } |
| else |
| return FALSE; |
| } |
| |
| 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, NULL, case_insensitive); |
| |
| search = *iter; |
| |
| do |
| { |
| /* This loop has an inefficient worst-case, where |
| * gtk_text_iter_get_text() is called repeatedly on |
| * a single line. |
| */ |
| GtkTextIter end; |
| |
| if (limit && |
| gtk_text_iter_compare (&search, limit) >= 0) |
| break; |
| |
| if (lines_match (&search, (const gchar**)lines, |
| visible_only, slice, case_insensitive, &match, &end)) |
| { |
| if (limit == NULL || |
| (limit && |
| gtk_text_iter_compare (&end, limit) <= 0)) |
| { |
| retval = TRUE; |
| |
| if (match_start) |
| *match_start = match; |
| |
| if (match_end) |
| *match_end = end; |
| } |
| |
| break; |
| } |
| } |
| while (gtk_text_iter_forward_line (&search)); |
| |
| g_strfreev ((gchar**)lines); |
| |
| return retval; |
| } |
| |
| static gboolean |
| vectors_equal_ignoring_trailing (gchar **vec1, |
| gchar **vec2, |
| gboolean case_insensitive) |
| { |
| /* Ignores trailing chars in vec2's last line */ |
| |
| gchar **i1, **i2; |
| |
| i1 = vec1; |
| i2 = vec2; |
| |
| while (*i1 && *i2) |
| { |
| gint len1; |
| gint len2; |
| |
| if (!case_insensitive) |
| { |
| if (strcmp (*i1, *i2) != 0) |
| { |
| if (*(i2 + 1) == NULL) /* if this is the last line */ |
| { |
| 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 |
| { |
| len1 = strlen (*i1); |
| len2 = strlen (*i2); |
| |
| if (!utf8_caselessnmatch (*i1, *i2, len1, len2)) |
| { |
| 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; |
| else |
| return TRUE; |
| } |
| |
| typedef struct _LinesWindow LinesWindow; |
| |
| struct _LinesWindow |
| { |
| gint n_lines; |
| gchar **lines; |
| |
| GtkTextIter first_line_start; |
| GtkTextIter first_line_end; |
| |
| guint slice : 1; |
| guint visible_only : 1; |
| }; |
| |
| static void |
| lines_window_init (LinesWindow *win, |
| const GtkTextIter *start) |
| { |
| gint i; |
| GtkTextIter line_start; |
| GtkTextIter line_end; |
| |
| /* If we start on line 1, there are 2 lines to search (0 and 1), so |
| * n_lines can be 2. |
| */ |
| if (gtk_text_iter_is_start (start) || |
| gtk_text_iter_get_line (start) + 1 < win->n_lines) |
| { |
| /* Already at the end, or not enough lines to match */ |
| win->lines = g_new0 (gchar*, 1); |
| *win->lines = NULL; |
| return; |
| } |
| |
| line_start = *start; |
| line_end = *start; |
| |
| /* Move to start iter to start of line */ |
| gtk_text_iter_set_line_offset (&line_start, 0); |
| |
| if (gtk_text_iter_equal (&line_start, &line_end)) |
| { |
| /* we were already at the start; so go back one line */ |
| gtk_text_iter_backward_line (&line_start); |
| } |
| |
| win->first_line_start = line_start; |
| win->first_line_end = line_end; |
| |
| win->lines = g_new0 (gchar*, win->n_lines + 1); |
| |
| i = win->n_lines - 1; |
| while (i >= 0) |
| { |
| gchar *line_text; |
| |
| if (win->slice) |
| { |
| if (win->visible_only) |
| line_text = gtk_text_iter_get_visible_slice (&line_start, &line_end); |
| else |
| line_text = gtk_text_iter_get_slice (&line_start, &line_end); |
| } |
| else |
| { |
| if (win->visible_only) |
| line_text = gtk_text_iter_get_visible_text (&line_start, &line_end); |
| else |
| line_text = gtk_text_iter_get_text (&line_start, &line_end); |
| } |
| |
| 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); |
| |
| --i; |
| } |
| } |
| |
| static gboolean |
| lines_window_back (LinesWindow *win) |
| { |
| GtkTextIter new_start; |
| gchar *line_text; |
| |
| new_start = win->first_line_start; |
| |
| if (!gtk_text_iter_backward_line (&new_start)) |
| return FALSE; |
| else |
| { |
| win->first_line_start = new_start; |
| win->first_line_end = new_start; |
| |
| gtk_text_iter_forward_line (&win->first_line_end); |
| } |
| |
| if (win->slice) |
| { |
| if (win->visible_only) |
| line_text = gtk_text_iter_get_visible_slice (&win->first_line_start, |
| &win->first_line_end); |
| else |
| line_text = gtk_text_iter_get_slice (&win->first_line_start, |
| &win->first_line_end); |
| } |
| else |
| { |
| if (win->visible_only) |
| line_text = gtk_text_iter_get_visible_text (&win->first_line_start, |
| &win->first_line_end); |
| else |
| line_text = gtk_text_iter_get_text (&win->first_line_start, |
| &win->first_line_end); |
| } |
| |
| /* Move lines to make room for first line. */ |
| memmove (win->lines + 1, win->lines, win->n_lines * sizeof (gchar*)); |
| |
| *win->lines = line_text; |
| |
| /* Free old last line and NULL-terminate */ |
| g_free (win->lines[win->n_lines]); |
| win->lines[win->n_lines] = NULL; |
| |
| return TRUE; |
| } |
| |
| static void |
| lines_window_free (LinesWindow *win) |
| { |
| g_strfreev (win->lines); |
| } |
| |
| /** |
| * gtk_text_iter_backward_search: |
| * @iter: a #GtkTextIter where the search begins |
| * @str: search string |
| * @flags: bitmask of flags affecting the search |
| * @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. |
| * |
| * @match_end will never be set to a #GtkTextIter located after @iter, even if |
| * there is a possible @match_start before or at @iter. |
| * |
| * Returns: whether a match was found |
| **/ |
| gboolean |
| gtk_text_iter_backward_search (const GtkTextIter *iter, |
| const gchar *str, |
| GtkTextSearchFlags flags, |
| GtkTextIter *match_start, |
| GtkTextIter *match_end, |
| const GtkTextIter *limit) |
| { |
| gchar **lines = NULL; |
| gchar **l; |
| gint n_lines; |
| LinesWindow win; |
| 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); |
| |
| if (limit && |
| gtk_text_iter_compare (limit, iter) > 0) |
| return FALSE; |
| |
| if (*str == '\0') |
| { |
| /* If we can move one char, return the empty string there */ |
| GtkTextIter match = *iter; |
| |
| if (limit && gtk_text_iter_equal (limit, &match)) |
| return FALSE; |
| |
| if (gtk_text_iter_backward_char (&match)) |
| { |
| if (match_start) |
| *match_start = match; |
| if (match_end) |
| *match_end = match; |
| return TRUE; |
| } |
| else |
| return FALSE; |
| } |
| |
| 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, &n_lines, case_insensitive); |
| |
| win.n_lines = n_lines; |
| win.slice = slice; |
| win.visible_only = visible_only; |
| |
| lines_window_init (&win, iter); |
| |
| if (*win.lines == NULL) |
| goto out; |
| |
| do |
| { |
| const gchar *first_line_match; |
| |
| if (limit && |
| gtk_text_iter_compare (limit, &win.first_line_end) > 0) |
| { |
| /* We're now before the search limit, abort. */ |
| goto out; |
| } |
| |
| /* If there are multiple lines, the first line will |
| * end in '\n', so this will only match at the |
| * end of the first line, which is correct. |
| */ |
| 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, |
| case_insensitive)) |
| { |
| /* Match! */ |
| gint offset; |
| GtkTextIter start_tmp; |
| GtkTextIter end_tmp; |
| |
| /* Offset to start of search string */ |
| offset = g_utf8_strlen (*win.lines, first_line_match - *win.lines); |
| |
| start_tmp = win.first_line_start; |
| forward_chars_with_skipping (&start_tmp, offset, |
| visible_only, !slice, FALSE); |
| |
| if (limit && |
| gtk_text_iter_compare (limit, &start_tmp) > 0) |
| goto out; /* match was bogus */ |
| |
| if (match_start) |
| *match_start = start_tmp; |
| |
| /* Go to end of search string */ |
| offset = 0; |
| for (l = lines; *l != NULL; l++) |
| offset += g_utf8_strlen (*l, -1); |
| |
| end_tmp = start_tmp; |
| forward_chars_with_skipping (&end_tmp, offset, |
| visible_only, !slice, case_insensitive); |
| |
| if (match_end) |
| *match_end = end_tmp; |
| |
| retval = TRUE; |
| goto out; |
| } |
| } |
| while (lines_window_back (&win)); |
| |
| out: |
| lines_window_free (&win); |
| g_strfreev (lines); |
| |
| return retval; |
| } |
| |
| /* |
| * Comparisons |
| */ |
| |
| /** |
| * gtk_text_iter_equal: |
| * @lhs: a #GtkTextIter |
| * @rhs: another #GtkTextIter |
| * |
| * Tests whether two iterators are equal, using the fastest possible |
| * mechanism. This function is very fast; you can expect it to perform |
| * better than e.g. getting the character offset for each iterator and |
| * comparing the offsets yourself. Also, it’s a bit faster than |
| * gtk_text_iter_compare(). |
| * |
| * Returns: %TRUE if the iterators point to the same place in the buffer |
| **/ |
| gboolean |
| gtk_text_iter_equal (const GtkTextIter *lhs, |
| const GtkTextIter *rhs) |
| { |
| GtkTextRealIter *real_lhs; |
| GtkTextRealIter *real_rhs; |
| |
| real_lhs = (GtkTextRealIter*)lhs; |
| real_rhs = (GtkTextRealIter*)rhs; |
| |
| check_invariants (lhs); |
| check_invariants (rhs); |
| |
| if (real_lhs->line != real_rhs->line) |
| return FALSE; |
| else if (real_lhs->line_byte_offset >= 0 && |
| real_rhs->line_byte_offset >= 0) |
| return real_lhs->line_byte_offset == real_rhs->line_byte_offset; |
| else |
| { |
| /* the ensure_char_offsets() calls do nothing if the char offsets |
| are already up-to-date. */ |
| ensure_char_offsets (real_lhs); |
| ensure_char_offsets (real_rhs); |
| return real_lhs->line_char_offset == real_rhs->line_char_offset; |
| } |
| } |
| |
| /** |
| * gtk_text_iter_compare: |
| * @lhs: a #GtkTextIter |
| * @rhs: another #GtkTextIter |
| * |
| * A qsort()-style function that returns negative if @lhs is less than |
| * @rhs, positive if @lhs is greater than @rhs, and 0 if they’re equal. |
| * Ordering is in character offset order, i.e. the first character in the buffer |
| * is less than the second character in the buffer. |
| * |
| * Returns: -1 if @lhs is less than @rhs, 1 if @lhs is greater, 0 if they are equal |
| **/ |
| gint |
| gtk_text_iter_compare (const GtkTextIter *lhs, |
| const GtkTextIter *rhs) |
| { |
| GtkTextRealIter *real_lhs; |
| GtkTextRealIter *real_rhs; |
| |
| real_lhs = gtk_text_iter_make_surreal (lhs); |
| real_rhs = gtk_text_iter_make_surreal (rhs); |
| |
| if (real_lhs == NULL || |
| real_rhs == NULL) |
| return -1; /* why not */ |
| |
| check_invariants (lhs); |
| check_invariants (rhs); |
| |
| if (real_lhs->line == real_rhs->line) |
| { |
| gint left_index, right_index; |
| |
| if (real_lhs->line_byte_offset >= 0 && |
| real_rhs->line_byte_offset >= 0) |
| { |
| left_index = real_lhs->line_byte_offset; |
| right_index = real_rhs->line_byte_offset; |
| } |
| else |
| { |
| /* the ensure_char_offsets() calls do nothing if |
| the offsets are already up-to-date. */ |
| ensure_char_offsets (real_lhs); |
| ensure_char_offsets (real_rhs); |
| left_index = real_lhs->line_char_offset; |
| right_index = real_rhs->line_char_offset; |
| } |
| |
| if (left_index < right_index) |
| return -1; |
| else if (left_index > right_index) |
| return 1; |
| else |
| return 0; |
| } |
| else |
| { |
| gint line1, line2; |
| |
| line1 = gtk_text_iter_get_line (lhs); |
| line2 = gtk_text_iter_get_line (rhs); |
| if (line1 < line2) |
| return -1; |
| else if (line1 > line2) |
| return 1; |
| else |
| return 0; |
| } |
| } |
| |
| /** |
| * gtk_text_iter_in_range: |
| * @iter: a #GtkTextIter |
| * @start: start of range |
| * @end: end of range |
| * |
| * Checks whether @iter falls in the range [@start, @end). |
| * @start and @end must be in ascending order. |
| * |
| * Returns: %TRUE if @iter is in the range |
| **/ |
| gboolean |
| gtk_text_iter_in_range (const GtkTextIter *iter, |
| const GtkTextIter *start, |
| const GtkTextIter *end) |
| { |
| g_return_val_if_fail (iter != NULL, FALSE); |
| g_return_val_if_fail (start != NULL, FALSE); |
| g_return_val_if_fail (end != NULL, FALSE); |
| g_return_val_if_fail (gtk_text_iter_compare (start, end) <= 0, FALSE); |
| |
| return gtk_text_iter_compare (iter, start) >= 0 && |
| gtk_text_iter_compare (iter, end) < 0; |
| } |
| |
| /** |
| * gtk_text_iter_order: |
| * @first: a #GtkTextIter |
| * @second: another #GtkTextIter |
| * |
| * Swaps the value of @first and @second if @second comes before |
| * @first in the buffer. That is, ensures that @first and @second are |
| * in sequence. Most text buffer functions that take a range call this |
| * automatically on your behalf, so there’s no real reason to call it yourself |
| * in those cases. There are some exceptions, such as gtk_text_iter_in_range(), |
| * that expect a pre-sorted range. |
| * |
| **/ |
| void |
| gtk_text_iter_order (GtkTextIter *first, |
| GtkTextIter *second) |
| { |
| g_return_if_fail (first != NULL); |
| g_return_if_fail (second != NULL); |
| |
| if (gtk_text_iter_compare (first, second) > 0) |
| { |
| GtkTextIter tmp; |
| |
| tmp = *first; |
| *first = *second; |
| *second = tmp; |
| } |
| } |
| |
| /* |
| * Init iterators from the BTree |
| */ |
| |
| void |
| _gtk_text_btree_get_iter_at_char (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| gint char_index) |
| { |
| GtkTextRealIter *real = (GtkTextRealIter*)iter; |
| gint real_char_index; |
| gint line_start; |
| GtkTextLine *line; |
| |
| g_return_if_fail (iter != NULL); |
| g_return_if_fail (tree != NULL); |
| |
| line = _gtk_text_btree_get_line_at_char (tree, char_index, |
| &line_start, &real_char_index); |
| |
| iter_init_from_char_offset (iter, tree, line, real_char_index - line_start); |
| |
| real->cached_char_index = real_char_index; |
| |
| check_invariants (iter); |
| } |
| |
| void |
| _gtk_text_btree_get_iter_at_line_char (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| gint line_number, |
| gint char_on_line) |
| { |
| GtkTextRealIter *real = (GtkTextRealIter*)iter; |
| GtkTextLine *line; |
| gint real_line; |
| |
| g_return_if_fail (iter != NULL); |
| g_return_if_fail (tree != NULL); |
| |
| line = _gtk_text_btree_get_line_no_last (tree, line_number, &real_line); |
| |
| iter_init_from_char_offset (iter, tree, line, char_on_line); |
| |
| /* We might as well cache this, since we know it. */ |
| real->cached_line_number = real_line; |
| |
| check_invariants (iter); |
| } |
| |
| void |
| _gtk_text_btree_get_iter_at_line_byte (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| gint line_number, |
| gint byte_index) |
| { |
| GtkTextRealIter *real = (GtkTextRealIter*)iter; |
| GtkTextLine *line; |
| gint real_line; |
| |
| g_return_if_fail (iter != NULL); |
| g_return_if_fail (tree != NULL); |
| |
| line = _gtk_text_btree_get_line_no_last (tree, line_number, &real_line); |
| |
| iter_init_from_byte_offset (iter, tree, line, byte_index); |
| |
| /* We might as well cache this, since we know it. */ |
| real->cached_line_number = real_line; |
| |
| check_invariants (iter); |
| } |
| |
| void |
| _gtk_text_btree_get_iter_at_line (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| GtkTextLine *line, |
| gint byte_offset) |
| { |
| g_return_if_fail (iter != NULL); |
| g_return_if_fail (tree != NULL); |
| g_return_if_fail (line != NULL); |
| |
| iter_init_from_byte_offset (iter, tree, line, byte_offset); |
| |
| check_invariants (iter); |
| } |
| |
| gboolean |
| _gtk_text_btree_get_iter_at_first_toggle (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| GtkTextTag *tag) |
| { |
| GtkTextLine *line; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| g_return_val_if_fail (tree != NULL, FALSE); |
| |
| line = _gtk_text_btree_first_could_contain_tag (tree, tag); |
| |
| if (line == NULL) |
| { |
| /* Set iter to last in tree */ |
| _gtk_text_btree_get_end_iter (tree, iter); |
| check_invariants (iter); |
| return FALSE; |
| } |
| else |
| { |
| iter_init_from_byte_offset (iter, tree, line, 0); |
| |
| if (!gtk_text_iter_toggles_tag (iter, tag)) |
| gtk_text_iter_forward_to_tag_toggle (iter, tag); |
| |
| check_invariants (iter); |
| return TRUE; |
| } |
| } |
| |
| gboolean |
| _gtk_text_btree_get_iter_at_last_toggle (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| GtkTextTag *tag) |
| { |
| gboolean found; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| g_return_val_if_fail (tree != NULL, FALSE); |
| |
| _gtk_text_btree_get_end_iter (tree, iter); |
| |
| if (gtk_text_iter_toggles_tag (iter, tag)) |
| found = TRUE; |
| else |
| found = gtk_text_iter_backward_to_tag_toggle (iter, tag); |
| |
| check_invariants (iter); |
| |
| return found; |
| } |
| |
| gboolean |
| _gtk_text_btree_get_iter_at_mark_name (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| const gchar *mark_name) |
| { |
| GtkTextMark *mark; |
| |
| g_return_val_if_fail (iter != NULL, FALSE); |
| g_return_val_if_fail (tree != NULL, FALSE); |
| |
| mark = _gtk_text_btree_get_mark_by_name (tree, mark_name); |
| |
| if (mark == NULL) |
| return FALSE; |
| else |
| { |
| _gtk_text_btree_get_iter_at_mark (tree, iter, mark); |
| check_invariants (iter); |
| return TRUE; |
| } |
| } |
| |
| void |
| _gtk_text_btree_get_iter_at_mark (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| GtkTextMark *mark) |
| { |
| GtkTextLineSegment *seg; |
| |
| g_return_if_fail (iter != NULL); |
| g_return_if_fail (tree != NULL); |
| g_return_if_fail (GTK_IS_TEXT_MARK (mark)); |
| |
| seg = mark->segment; |
| |
| iter_init_from_segment (iter, tree, |
| seg->body.mark.line, seg); |
| g_assert (seg->body.mark.line == _gtk_text_iter_get_text_line (iter)); |
| check_invariants (iter); |
| } |
| |
| void |
| _gtk_text_btree_get_iter_at_child_anchor (GtkTextBTree *tree, |
| GtkTextIter *iter, |
| GtkTextChildAnchor *anchor) |
| { |
| GtkTextLineSegment *seg; |
| |
| g_return_if_fail (iter != NULL); |
| g_return_if_fail (tree != NULL); |
| g_return_if_fail (GTK_IS_TEXT_CHILD_ANCHOR (anchor)); |
| |
| seg = anchor->segment; |
| |
| g_assert (seg->body.child.line != NULL); |
| |
| iter_init_from_segment (iter, tree, |
| seg->body.child.line, seg); |
| g_assert (seg->body.child.line == _gtk_text_iter_get_text_line (iter)); |
| check_invariants (iter); |
| } |
| |
| void |
| _gtk_text_btree_get_end_iter (GtkTextBTree *tree, |
| GtkTextIter *iter) |
| { |
| g_return_if_fail (iter != NULL); |
| g_return_if_fail (tree != NULL); |
| |
| _gtk_text_btree_get_iter_at_char (tree, |
| iter, |
| _gtk_text_btree_char_count (tree)); |
| check_invariants (iter); |
| } |
| |
| void |
| _gtk_text_iter_check (const GtkTextIter *iter) |
| { |
| const GtkTextRealIter *real = (const GtkTextRealIter*)iter; |
| gint line_char_offset, line_byte_offset, seg_char_offset, seg_byte_offset; |
| GtkTextLineSegment *byte_segment = NULL; |
| GtkTextLineSegment *byte_any_segment = NULL; |
| GtkTextLineSegment *char_segment = NULL; |
| GtkTextLineSegment *char_any_segment = NULL; |
| gboolean segments_updated; |
| |
| /* This function checks our class invariants for the Iter class. */ |
| |
| g_assert (sizeof (GtkTextIter) == sizeof (GtkTextRealIter)); |
| |
| if (real->chars_changed_stamp != |
| _gtk_text_btree_get_chars_changed_stamp (real->tree)) |
| g_error ("iterator check failed: invalid iterator"); |
| |
| if (real->line_char_offset < 0 && real->line_byte_offset < 0) |
| g_error ("iterator check failed: both char and byte offsets are invalid"); |
| |
| segments_updated = (real->segments_changed_stamp == |
| _gtk_text_btree_get_segments_changed_stamp (real->tree)); |
| |
| #if 0 |
| printf ("checking iter, segments %s updated, byte %d char %d\n", |
| segments_updated ? "are" : "aren't", |
| real->line_byte_offset, |
| real->line_char_offset); |
| #endif |
| |
| if (segments_updated) |
| { |
| if (real->segment_char_offset < 0 && real->segment_byte_offset < 0) |
| g_error ("iterator check failed: both char and byte segment offsets are invalid"); |
| |
| if (real->segment->char_count == 0) |
| g_error ("iterator check failed: segment is not indexable."); |
| |
| if (real->line_char_offset >= 0 && real->segment_char_offset < 0) |
| g_error ("segment char offset is not properly up-to-date"); |
| |
| if (real->line_byte_offset >= 0 && real->segment_byte_offset < 0) |
| g_error ("segment byte offset is not properly up-to-date"); |
| |
| if (real->segment_byte_offset >= 0 && |
| real->segment_byte_offset >= real->segment->byte_count) |
| g_error ("segment byte offset is too large."); |
| |
| if (real->segment_char_offset >= 0 && |
| real->segment_char_offset >= real->segment->char_count) |
| g_error ("segment char offset is too large."); |
| } |
| |
| if (real->line_byte_offset >= 0) |
| { |
| _gtk_text_line_byte_locate (real->line, real->line_byte_offset, |
| &byte_segment, &byte_any_segment, |
| &seg_byte_offset, &line_byte_offset); |
| |
| if (line_byte_offset != real->line_byte_offset) |
| g_error ("wrong byte offset was stored in iterator"); |
| |
| if (segments_updated) |
| { |
| if (real->segment != byte_segment) |
| g_error ("wrong segment was stored in iterator"); |
| |
| if (real->any_segment != byte_any_segment) |
| g_error ("wrong any_segment was stored in iterator"); |
| |
| if (seg_byte_offset != real->segment_byte_offset) |
| g_error ("wrong segment byte offset was stored in iterator"); |
| |
| if (byte_segment->type == >k_text_char_type) |
| { |
| const gchar *p; |
| p = byte_segment->body.chars + seg_byte_offset; |
| |
| if (!gtk_text_byte_begins_utf8_char (p)) |
| g_error ("broken iterator byte index pointed into the middle of a character"); |
| } |
| } |
| } |
| |
| if (real->line_char_offset >= 0) |
| { |
| _gtk_text_line_char_locate (real->line, real->line_char_offset, |
| &char_segment, &char_any_segment, |
| &seg_char_offset, &line_char_offset); |
| |
| if (line_char_offset != real->line_char_offset) |
| g_error ("wrong char offset was stored in iterator"); |
| |
| if (segments_updated) |
| { |
| if (real->segment != char_segment) |
| g_error ("wrong segment was stored in iterator"); |
| |
| if (real->any_segment != char_any_segment) |
| g_error ("wrong any_segment was stored in iterator"); |
| |
| if (seg_char_offset != real->segment_char_offset) |
| g_error ("wrong segment char offset was stored in iterator"); |
| |
| if (char_segment->type == >k_text_char_type) |
| { |
| const gchar *p; |
| p = g_utf8_offset_to_pointer (char_segment->body.chars, |
| seg_char_offset); |
| |
| /* hmm, not likely to happen eh */ |
| if (!gtk_text_byte_begins_utf8_char (p)) |
| g_error ("broken iterator char offset pointed into the middle of a character"); |
| } |
| } |
| } |
| |
| if (real->line_char_offset >= 0 && real->line_byte_offset >= 0) |
| { |
| if (byte_segment != char_segment) |
| g_error ("char and byte offsets did not point to the same segment"); |
| |
| if (byte_any_segment != char_any_segment) |
| g_error ("char and byte offsets did not point to the same any segment"); |
| |
| /* Make sure the segment offsets are equivalent, if it's a char |
| segment. */ |
| if (char_segment->type == >k_text_char_type) |
| { |
| gint byte_offset = 0; |
| gint char_offset = 0; |
| while (char_offset < seg_char_offset) |
| { |
| const char * start = char_segment->body.chars + byte_offset; |
| byte_offset += g_utf8_next_char (start) - start; |
| char_offset += 1; |
| } |
| |
| if (byte_offset != seg_byte_offset) |
| g_error ("byte offset did not correspond to char offset"); |
| |
| char_offset = |
| g_utf8_strlen (char_segment->body.chars, seg_byte_offset); |
| |
| if (char_offset != seg_char_offset) |
| g_error ("char offset did not correspond to byte offset"); |
| |
| if (!gtk_text_byte_begins_utf8_char (char_segment->body.chars + seg_byte_offset)) |
| g_error ("byte index for iterator does not index the start of a character"); |
| } |
| } |
| |
| if (real->cached_line_number >= 0) |
| { |
| gint should_be; |
| |
| should_be = _gtk_text_line_get_number (real->line); |
| if (real->cached_line_number != should_be) |
| g_error ("wrong line number was cached"); |
| } |
| |
| if (real->cached_char_index >= 0) |
| { |
| if (real->line_char_offset >= 0) /* only way we can check it |
| efficiently, not a real |
| invariant. */ |
| { |
| gint char_index; |
| |
| char_index = _gtk_text_line_char_index (real->line); |
| char_index += real->line_char_offset; |
| |
| if (real->cached_char_index != char_index) |
| g_error ("wrong char index was cached"); |
| } |
| } |
| |
| if (_gtk_text_line_is_last (real->line, real->tree)) |
| g_error ("Iterator was on last line (past the end iterator)"); |
| } |