diff options
author | Havoc Pennington <hp@redhat.com> | 2000-10-04 14:23:09 +0000 |
---|---|---|
committer | Havoc Pennington <hp@src.gnome.org> | 2000-10-04 14:23:09 +0000 |
commit | 928c069883b807b67e964b90aa8dd3d5f9bc5bb2 (patch) | |
tree | 5799591cec57c1abb9fb0f156721b9e546e07fca /gtk | |
parent | 69ac9451a6dac6513f1325122bdf9664040267c9 (diff) | |
download | gtk+-928c069883b807b67e964b90aa8dd3d5f9bc5bb2.tar.gz |
clean up trailing whitespace, add extensive tests for tag toggle
2000-10-03 Havoc Pennington <hp@redhat.com>
* gtk/testtextbuffer.c: clean up trailing whitespace,
add extensive tests for tag toggle iteration.
* gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan
distance
* gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
optimize case where the tag root is on level 1
(gtk_text_line_previous_could_contain_tag): attempt to implement
(gtk_text_line_next_could_contain_tag): Abstract out node_compare
functionality
* gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
Implement this, though not very efficiently.
* gtk/gtktextiterprivate.h: reformat
* gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
iterator
* gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
this to properly revalidate the iterators.
* gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
properly at the end of the line (and therefore on empty lines)
* gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
maybe we should return a value...
Diffstat (limited to 'gtk')
-rw-r--r-- | gtk/gtktextbtree.c | 457 | ||||
-rw-r--r-- | gtk/gtktextbuffer.c | 30 | ||||
-rw-r--r-- | gtk/gtktextiter.c | 460 | ||||
-rw-r--r-- | gtk/gtktextiterprivate.h | 19 | ||||
-rw-r--r-- | gtk/gtktextlayout.c | 8 | ||||
-rw-r--r-- | gtk/gtktextlayout.h | 2 | ||||
-rw-r--r-- | gtk/gtktextview.c | 22 | ||||
-rw-r--r-- | gtk/testtextbuffer.c | 440 |
8 files changed, 1202 insertions, 236 deletions
diff --git a/gtk/gtktextbtree.c b/gtk/gtktextbtree.c index a82f0d1caa..4fbf2ddd71 100644 --- a/gtk/gtktextbtree.c +++ b/gtk/gtktextbtree.c @@ -414,9 +414,9 @@ gtk_text_btree_new (GtkTextTagTable *table, tree->mark_table = g_hash_table_new(g_str_hash, g_str_equal); /* We don't ref the buffer, since the buffer owns us; - we'd have some circularity issues. The buffer always - lasts longer than the BTree - */ + * we'd have some circularity issues. The buffer always + * lasts longer than the BTree + */ tree->buffer = buffer; { @@ -434,7 +434,6 @@ gtk_text_btree_new (GtkTextTagTable *table, FALSE); tree->insert_mark->body.mark.not_deleteable = TRUE; - tree->insert_mark->body.mark.visible = TRUE; tree->selection_bound_mark = @@ -2486,6 +2485,8 @@ gtk_text_btree_get_selection_bounds (GtkTextBTree *tree, if (end) *end = tmp_end; + + return FALSE; } else { @@ -2502,7 +2503,7 @@ gtk_text_btree_get_selection_bounds (GtkTextBTree *tree, } void -gtk_text_btree_place_cursor(GtkTextBTree *tree, +gtk_text_btree_place_cursor(GtkTextBTree *tree, const GtkTextIter *iter) { GtkTextIter start, end; @@ -2585,7 +2586,7 @@ gtk_text_btree_get_mark_by_name (GtkTextBTree *tree, void gtk_text_mark_set_visible (GtkTextMark *mark, - gboolean setting) + gboolean setting) { GtkTextLineSegment *seg; @@ -2599,7 +2600,7 @@ gtk_text_mark_set_visible (GtkTextMark *mark, { seg->body.mark.visible = setting; - redisplay_mark(seg); + redisplay_mark (seg); } } @@ -3052,12 +3053,12 @@ gtk_text_line_previous (GtkTextLine *line) return prev; } - g_assert_not_reached(); + g_assert_not_reached (); return NULL; } void -gtk_text_line_add_data (GtkTextLine *line, +gtk_text_line_add_data (GtkTextLine *line, GtkTextLineData *data) { g_return_if_fail(line != NULL); @@ -3418,7 +3419,6 @@ gtk_text_line_char_to_byte (GtkTextLine *line, g_warning("FIXME not implemented"); } - /* FIXME sync with char_locate (or figure out a clean way to merge the two functions) */ void @@ -3692,6 +3692,101 @@ gtk_text_line_char_to_byte_offsets(GtkTextLine *line, } } +static gint +node_compare (GtkTextBTreeNode *lhs, + GtkTextBTreeNode *rhs) +{ + GtkTextBTreeNode *iter; + GtkTextBTreeNode *node; + GtkTextBTreeNode *common_parent; + GtkTextBTreeNode *parent_of_lower; + GtkTextBTreeNode *parent_of_higher; + gboolean lhs_is_lower; + GtkTextBTreeNode *lower; + GtkTextBTreeNode *higher; + + /* This function assumes that lhs and rhs are not underneath each + * other. + */ + + if (lhs == rhs) + return 0; + + if (lhs->level < rhs->level) + { + lhs_is_lower = TRUE; + lower = lhs; + higher = rhs; + } + else + { + lhs_is_lower = FALSE; + lower = rhs; + higher = lhs; + } + + /* Algorithm: find common parent of lhs/rhs. Save the child nodes + * of the common parent we used to reach the common parent; the + * ordering of these child nodes in the child list is the ordering + * of lhs and rhs. + */ + + /* Get on the same level (may be on same level already) */ + node = lower; + while (node->level < higher->level) + node = node->parent; + + g_assert (node->level == higher->level); + + g_assert (node != higher); /* Happens if lower is underneath higher */ + + /* Go up until we have two children with a common parent. + */ + parent_of_lower = node; + parent_of_higher = higher; + + while (parent_of_lower->parent != parent_of_higher->parent) + { + parent_of_lower = parent_of_lower->parent; + parent_of_higher = parent_of_higher->parent; + } + + g_assert (parent_of_lower->parent == parent_of_higher->parent); + + common_parent = parent_of_lower->parent; + + g_assert (common_parent != NULL); + + /* See which is first in the list of common_parent's children */ + iter = common_parent->children.node; + while (iter != NULL) + { + if (iter == parent_of_higher) + { + /* higher is less than lower */ + + if (lhs_is_lower) + return 1; /* lhs > rhs */ + else + return -1; + } + else if (iter == parent_of_lower) + { + /* lower is less than higher */ + + if (lhs_is_lower) + return -1; /* lhs < rhs */ + else + return 1; + } + + iter = iter->next; + } + + g_assert_not_reached (); + return 0; +} + /* remember that tag == NULL means "any tag" */ GtkTextLine* gtk_text_line_next_could_contain_tag(GtkTextLine *line, @@ -3710,7 +3805,8 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line, if (tag == NULL) { /* Right now we can only offer linear-search if the user wants - to know about any tag toggle at all. */ + * to know about any tag toggle at all. + */ return gtk_text_line_next (line); } @@ -3733,6 +3829,9 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line, if (info->tag_root == NULL) return NULL; + + if (info->tag_root == line->parent) + return NULL; /* we were at the last line under the tag root */ /* We need to go up out of this node, and on to the next one with toggles for the target tag. If we're below the tag root, we need to @@ -3773,57 +3872,25 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line, } else { - GtkTextBTreeNode * iter; - GtkTextBTreeNode * common_parent; - GtkTextBTreeNode * parent_of_tag_root; - GtkTextBTreeNode * parent_of_node; - - /* Find common parent between our current line, and the tag - root. Save the child nodes of the common parent we used to get - to the common parent; we then use these two child nodes to - determine whether the ordering of the tag root and the current - line in the tree. (Nice code cleanup: write - gtk_btree_node_compare() to compute node ordering.) - */ - - /* Get on the same level */ - node = line->parent; - while (node->level < info->tag_root->level) - node = node->parent; - - common_parent = info->tag_root->parent; + gint ordering; - /* Find common parent, and children of that parent above - tag root and our current node */ - parent_of_node = node; - parent_of_tag_root = info->tag_root; + ordering = node_compare (line->parent, info->tag_root); - while (node->parent != common_parent) + if (ordering < 0) { - parent_of_node = node; - parent_of_tag_root = common_parent; - node = node->parent; - common_parent = common_parent->parent; + /* Tag root is ahead of us, so search there. */ + node = info->tag_root; + goto found; } - - /* See which is first in the list of common_parent's children */ - iter = common_parent->children.node; - while (iter != NULL) + else { - if (iter == parent_of_tag_root) - return NULL; /* Tag root was before us in the tree */ - else if (iter == parent_of_node) - { - /* We want the first inside-tag-root node, - since we're before the tag root */ - node = info->tag_root; - goto found; - } - - iter = iter->next; + /* Tag root is after us, so no more lines that + * could contain the tag. + */ + return NULL; } - - return NULL; + + g_assert_not_reached (); } found: @@ -3831,42 +3898,261 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line, g_assert(node != NULL); /* We have to find the first sub-node of this node that contains - the target tag. */ + * the target tag. + */ - continue_outer_loop: while (node->level > 0) { - g_assert(node != NULL); /* If this fails, it likely means an - incorrect tag summary led us on a - wild goose chase down this branch of - the tree. */ + g_assert (node != NULL); /* If this fails, it likely means an + incorrect tag summary led us on a + wild goose chase down this branch of + the tree. */ node = node->children.node; while (node != NULL) { - if (gtk_text_btree_node_has_tag(node, tag)) - goto continue_outer_loop; + if (gtk_text_btree_node_has_tag (node, tag)) + break; node = node->next; } - g_assert(node != NULL); } - g_assert(node != NULL); - g_assert(node->level == 0); + g_assert (node != NULL); + g_assert (node->level == 0); return node->children.line; } - + +static GtkTextLine* +prev_line_under_node (GtkTextBTreeNode *node, + GtkTextLine *line) +{ + GtkTextLine *prev; + + prev = node->children.line; + + g_assert (prev); + + if (prev != line) + { + while (prev->next != line) + prev = prev->next; + + return prev; + } + + return NULL; +} + GtkTextLine* -gtk_text_line_previous_could_contain_tag(GtkTextLine *line, - GtkTextBTree *tree, - GtkTextTag *tag) +gtk_text_line_previous_could_contain_tag (GtkTextLine *line, + GtkTextBTree *tree, + GtkTextTag *tag) { - g_warning("FIXME"); + GtkTextBTreeNode *node; + GtkTextBTreeNode *found_node = NULL; + GtkTextTagInfo *info; + gboolean below_tag_root; + GtkTextLine *prev; + GtkTextBTreeNode *line_ancestor; + GtkTextBTreeNode *line_ancestor_parent; + + /* See next_could_contain_tag() for more extensive comments + * on what's going on here. + */ + + g_return_val_if_fail(line != NULL, NULL); + + if (gtk_debug_flags & GTK_DEBUG_TEXT) + gtk_text_btree_check (tree); + + if (tag == NULL) + { + /* Right now we can only offer linear-search if the user wants + * to know about any tag toggle at all. + */ + return gtk_text_line_previous (line); + } + + /* Return same-node line, if any. */ + prev = prev_line_under_node (line->parent, line); + if (prev) + return prev; + + info = gtk_text_btree_get_existing_tag_info (tree, tag); + if (info == NULL) + return NULL; + + if (info->tag_root == NULL) + return NULL; + + if (info->tag_root == line->parent) + return NULL; /* we were at the first line under the tag root */ + + /* Are we below the tag root */ + node = line->parent; + below_tag_root = FALSE; + while (node != NULL) + { + if (node == info->tag_root) + { + below_tag_root = TRUE; + break; + } + + node = node->parent; + } + + if (below_tag_root) + { + /* Look for a previous node under this tag root that has our + * tag. + */ + + /* this assertion holds because line->parent is not the + * tag root, we are below the tag root, and the tag + * root exists. + */ + g_assert (line->parent->parent != NULL); + + line_ancestor = line->parent; + line_ancestor_parent = line->parent->parent; + + node = line_ancestor_parent->children.node; + while (node != line_ancestor && + line_ancestor != info->tag_root) + { + GSList *child_nodes = NULL; + GSList *tmp; + + /* Create reverse-order list of nodes before + * line_ancestor + */ + while (node != line_ancestor + && node != NULL) + { + child_nodes = g_slist_prepend (child_nodes, node); + + node = node->next; + } + + /* Try to find a node with our tag on it in the list */ + tmp = child_nodes; + while (tmp != NULL) + { + GtkTextBTreeNode *this_node = tmp->data; + + g_assert (this_node != line_ancestor); + + if (gtk_text_btree_node_has_tag (this_node, tag)) + { + found_node = this_node; + g_slist_free (child_nodes); + goto found; + } + + tmp = g_slist_next (tmp); + } + + g_slist_free (child_nodes); + + /* Didn't find anything on this level; go up one level. */ + line_ancestor = line_ancestor_parent; + line_ancestor_parent = line_ancestor->parent; + + node = line_ancestor_parent->children.node; + } + /* No dice. */ + return NULL; + } + else + { + gint ordering; + + ordering = node_compare (line->parent, info->tag_root); + + if (ordering < 0) + { + /* Tag root is ahead of us, so no more lines + * with this tag. + */ + return NULL; + } + else + { + /* Tag root is after us, so grab last tagged + * line underneath the tag root. + */ + found_node = info->tag_root; + goto found; + } + + g_assert_not_reached (); + } + + found: + + g_assert (found_node != NULL); + + /* We have to find the last sub-node of this node that contains + * the target tag. + */ + node = found_node; + + while (node->level > 0) + { + GSList *child_nodes = NULL; + GSList *iter; + g_assert (node != NULL); /* If this fails, it likely means an + incorrect tag summary led us on a + wild goose chase down this branch of + the tree. */ + + node = node->children.node; + while (node != NULL) + { + child_nodes = g_slist_prepend (child_nodes, node); + node = node->next; + } + + node = NULL; /* detect failure to find a child node. */ + + iter = child_nodes; + while (iter != NULL) + { + if (gtk_text_btree_node_has_tag (iter->data, tag)) + { + /* recurse into this node. */ + node = iter->data; + break; + } + + iter = g_slist_next (iter); + } + + g_slist_free (child_nodes); + + g_assert (node != NULL); + } + + g_assert (node != NULL); + g_assert (node->level == 0); + + /* this assertion is correct, but slow. */ + /* g_assert (node_compare (node, line->parent) < 0); */ + + /* Return last line in this node. */ + + prev = node->children.line; + while (prev->next) + prev = prev->next; + + return prev; } /* - * Non-public function implementations */ + * Non-public function implementations + */ static void summary_list_destroy(Summary *summary) @@ -6260,6 +6546,11 @@ gtk_text_btree_spew (GtkTextBTree *tree) list = g_slist_next (list); } + + if (tree->tag_infos == NULL) + { + printf (" (no tags in the tree)\n"); + } } printf("=================== Tree nodes\n"); @@ -6301,11 +6592,17 @@ gtk_text_btree_spew_line_short (GtkTextLine *line, int indent) } else if (seg->type == >k_text_right_mark_type) { - printf("%s right mark `%s'\n", spaces, seg->body.mark.name); + printf("%s right mark `%s' visible: %d\n", + spaces, + seg->body.mark.name, + seg->body.mark.visible); } else if (seg->type == >k_text_left_mark_type) { - printf("%s left mark `%s'\n", spaces, seg->body.mark.name); + printf("%s left mark `%s' visible: %d\n", + spaces, + seg->body.mark.name, + seg->body.mark.visible); } else if (seg->type == >k_text_toggle_on_type || seg->type == >k_text_toggle_off_type) @@ -6397,11 +6694,17 @@ gtk_text_btree_spew_segment(GtkTextBTree* tree, GtkTextLineSegment * seg) } else if (seg->type == >k_text_right_mark_type) { - printf(" right mark `%s'\n", seg->body.mark.name); + printf(" right mark `%s' visible: %d not_deleteable: %d\n", + seg->body.mark.name, + seg->body.mark.visible, + seg->body.mark.not_deleteable); } else if (seg->type == >k_text_left_mark_type) { - printf(" left mark `%s'\n", seg->body.mark.name); + printf(" left mark `%s' visible: %d not_deleteable: %d\n", + seg->body.mark.name, + seg->body.mark.visible, + seg->body.mark.not_deleteable); } else if (seg->type == >k_text_toggle_on_type || seg->type == >k_text_toggle_off_type) diff --git a/gtk/gtktextbuffer.c b/gtk/gtktextbuffer.c index 9a70c78a10..627341f431 100644 --- a/gtk/gtktextbuffer.c +++ b/gtk/gtktextbuffer.c @@ -821,8 +821,8 @@ gtk_text_buffer_emit_delete (GtkTextBuffer *buffer, **/ void gtk_text_buffer_delete (GtkTextBuffer *buffer, - GtkTextIter *start, - GtkTextIter *end) + GtkTextIter *start, + GtkTextIter *end) { g_return_if_fail(GTK_IS_TEXT_BUFFER(buffer)); g_return_if_fail(start != NULL); @@ -840,7 +840,9 @@ gtk_text_buffer_delete (GtkTextBuffer *buffer, * * Deletes all <emphasis>editable</emphasis> text in the given range. * Calls gtk_text_buffer_delete() for each editable sub-range of - * [@start,@end). + * [@start,@end). @start and @end are revalidated to point to + * the location of the last deleted range, or left untouched if + * no text was deleted. * * Return value: whether some text was actually deleted **/ @@ -904,6 +906,10 @@ gtk_text_buffer_delete_interactive (GtkTextBuffer *buffer, gtk_text_buffer_emit_delete (buffer, &start, &iter, TRUE); deleted_stuff = TRUE; + + /* revalidate user's iterators. */ + *start_iter = start; + *end_iter = iter; } break; @@ -923,6 +929,10 @@ gtk_text_buffer_delete_interactive (GtkTextBuffer *buffer, current_state = FALSE; deleted_stuff = TRUE; + + /* revalidate user's iterators. */ + *start_iter = start; + *end_iter = iter; } else { @@ -1478,7 +1488,7 @@ gtk_text_buffer_real_apply_tag (GtkTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end) { - gtk_text_btree_tag(start, end, tag, TRUE); + gtk_text_btree_tag (start, end, tag, TRUE); } static void @@ -1487,7 +1497,7 @@ gtk_text_buffer_real_remove_tag (GtkTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end) { - gtk_text_btree_tag(start, end, tag, FALSE); + gtk_text_btree_tag (start, end, tag, FALSE); } @@ -1500,12 +1510,14 @@ gtk_text_buffer_emit_tag(GtkTextBuffer *buffer, { g_return_if_fail(tag != NULL); + gtk_text_iter_reorder (start, end); + if (apply) - gtk_signal_emit(GTK_OBJECT(buffer), signals[APPLY_TAG], - tag, start, end); + gtk_signal_emit (GTK_OBJECT(buffer), signals[APPLY_TAG], + tag, start, end); else - gtk_signal_emit(GTK_OBJECT(buffer), signals[REMOVE_TAG], - tag, start, end); + gtk_signal_emit (GTK_OBJECT(buffer), signals[REMOVE_TAG], + tag, start, end); } diff --git a/gtk/gtktextiter.c b/gtk/gtktextiter.c index b2ec510fe8..e061e1978e 100644 --- a/gtk/gtktextiter.c +++ b/gtk/gtktextiter.c @@ -33,7 +33,8 @@ typedef struct _GtkTextRealIter GtkTextRealIter; -struct _GtkTextRealIter { +struct _GtkTextRealIter +{ /* Always-valid information */ GtkTextBTree *tree; GtkTextLine *line; @@ -53,7 +54,7 @@ struct _GtkTextRealIter { /* 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" */ + maybe same as "segment" */ /* One of these will always be valid if segments_changed_stamp is up-to-date. If invalid, they are -1. @@ -94,7 +95,7 @@ iter_set_from_byte_offset(GtkTextRealIter *iter, { iter_set_common(iter, line); - gtk_text_line_byte_locate(iter->line, + gtk_text_line_byte_locate (iter->line, byte_offset, &iter->segment, &iter->any_segment, @@ -110,7 +111,7 @@ iter_set_from_char_offset(GtkTextRealIter *iter, { iter_set_common(iter, line); - gtk_text_line_char_locate(iter->line, + gtk_text_line_char_locate (iter->line, char_offset, &iter->segment, &iter->any_segment, @@ -362,6 +363,12 @@ ensure_byte_offsets(GtkTextRealIter *iter) } } +static inline gboolean +is_segment_start (GtkTextRealIter *real) +{ + return real->segment_byte_offset == 0 || real->segment_char_offset == 0; +} + #if 1 static void check_invariants(const GtkTextIter *iter) @@ -689,12 +696,15 @@ gtk_text_iter_get_line_index(const GtkTextIter *iter) * Returns the Unicode character at this iterator. (Equivalent to * operator* on a C++ iterator.) If the iterator points at a * non-character element, such as an image embedded in the buffer, the - * Unicode "unknown" character 0xFFFD is returned. + * Unicode "unknown" character 0xFFFD 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. * - * Return value: a Unicode character + * Return value: a Unicode character, or 0 if @iter is not dereferenceable **/ gunichar -gtk_text_iter_get_char(const GtkTextIter *iter) +gtk_text_iter_get_char (const GtkTextIter *iter) { GtkTextRealIter *real; @@ -705,12 +715,11 @@ gtk_text_iter_get_char(const GtkTextIter *iter) if (real == NULL) return 0; - check_invariants(iter); - - /* FIXME probably want to special-case the end iterator - and either have an error or return 0 */ - - if (real->segment->type == >k_text_char_type) + check_invariants(iter); + + if (gtk_text_iter_is_last (iter)) + return 0; + else if (real->segment->type == >k_text_char_type) { ensure_byte_offsets(real); @@ -1392,13 +1401,13 @@ gtk_text_iter_get_attributes (const GtkTextIter *iter, * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE) */ static gboolean -forward_line_leaving_caches_unmodified(GtkTextRealIter *real) +forward_line_leaving_caches_unmodified (GtkTextRealIter *real) { GtkTextLine *new_line; - new_line = gtk_text_line_next(real->line); + new_line = gtk_text_line_next (real->line); - g_assert(new_line != real->line); + g_assert (new_line != real->line); if (new_line != NULL) { @@ -1442,11 +1451,56 @@ forward_line_leaving_caches_unmodified(GtkTextRealIter *real) } } + +/* The return value of this indicates WHETHER WE MOVED. + * The return value of public functions indicates + * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE) + */ +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; + } +} + /* The return value indicates (MOVEMENT OCCURRED && NEW ITER IS * DEREFERENCEABLE) */ static gboolean -forward_char(GtkTextRealIter *real) +forward_char (GtkTextRealIter *real) { GtkTextIter *iter = (GtkTextIter*)real; @@ -1465,7 +1519,7 @@ forward_char(GtkTextRealIter *real) /* 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); + g_assert (real->segment->type == >k_text_char_type); if (real->line_byte_offset >= 0) { @@ -1502,7 +1556,7 @@ forward_char(GtkTextRealIter *real) } gboolean -gtk_text_iter_forward_indexable_segment(GtkTextIter *iter) +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. */ @@ -1524,7 +1578,7 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter) if (real->line_char_offset >= 0) { chars_skipped = real->segment->char_count - real->segment_char_offset; - g_assert(chars_skipped > 0); + g_assert (chars_skipped > 0); } else chars_skipped = 0; @@ -1532,7 +1586,7 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter) if (real->line_byte_offset >= 0) { bytes_skipped = real->segment->byte_count - real->segment_byte_offset; - g_assert(bytes_skipped > 0); + g_assert (bytes_skipped > 0); } else bytes_skipped = 0; @@ -1551,17 +1605,17 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter) if (real->line_byte_offset >= 0) { - g_assert(bytes_skipped > 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); + g_assert (chars_skipped > 0); real->segment_char_offset = 0; real->line_char_offset += chars_skipped; - adjust_char_index(real, chars_skipped); + adjust_char_index (real, chars_skipped); } check_invariants(iter); @@ -1571,21 +1625,19 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter) else { /* End of the line */ - if (forward_line_leaving_caches_unmodified(real)) + if (forward_line_leaving_caches_unmodified (real)) { - adjust_line_number(real, 1); + adjust_line_number (real, 1); if (real->line_char_offset >= 0) - adjust_char_index(real, chars_skipped); - - check_invariants(iter); + 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)); + 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); + check_invariants (iter); if (gtk_text_iter_is_last (iter)) return FALSE; @@ -1603,13 +1655,156 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter) } } +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) +gtk_text_iter_backward_indexable_segment (GtkTextIter *iter) { - g_warning("FIXME"); + /* 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); - return FALSE; + 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); + while (seg != real->segment) + { + prev_seg = seg; + prev_any_seg = any_seg; + + any_seg = seg->next; + seg = any_seg; + while (seg->char_count == 0) + seg = seg->next; + } + + 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; } /** @@ -1677,7 +1872,7 @@ gtk_text_iter_prev_char(GtkTextIter *iter) I guess you'd have to profile the various approaches. */ -#define MAX_LINEAR_SCAN 300 +#define MAX_LINEAR_SCAN 150 /** @@ -1822,23 +2017,34 @@ gtk_text_iter_backward_chars(GtkTextIter *iter, gint count) else { /* We need to go back into previous segments. For now, - just keep this really simple. */ - gint current_char_index; - gint new_char_index; + * 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); + current_char_index = gtk_text_iter_get_offset (iter); - if (current_char_index == 0) - return FALSE; /* can't move backward */ + 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); + 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); + check_invariants(iter); - return TRUE; + return TRUE; + } + else + { + /* FIXME backward_indexable_segment here */ + + return FALSE; + } } } @@ -1935,7 +2141,7 @@ gtk_text_iter_backward_line(GtkTextIter *iter) return FALSE; } - invalidate_char_index(real); + invalidate_char_index (real); real->line_byte_offset = 0; real->line_char_offset = 0; @@ -1946,7 +2152,7 @@ gtk_text_iter_backward_line(GtkTextIter *iter) /* Find first segment in line */ real->any_segment = real->line->segments; real->segment = gtk_text_line_byte_to_segment(real->line, - 0, &offset); + 0, &offset); g_assert(offset == 0); @@ -2207,18 +2413,18 @@ gtk_text_iter_set_line_offset(GtkTextIter *iter, { GtkTextRealIter *real; - g_return_if_fail(iter != NULL); + g_return_if_fail (iter != NULL); - real = gtk_text_iter_make_surreal(iter); + real = gtk_text_iter_make_surreal (iter); if (real == NULL) return; - check_invariants(iter); + check_invariants (iter); - iter_set_from_char_offset(real, real->line, char_on_line); + iter_set_from_char_offset (real, real->line, char_on_line); - check_invariants(iter); + check_invariants (iter); } void @@ -2228,23 +2434,23 @@ gtk_text_iter_set_line(GtkTextIter *iter, gint line_number) gint real_line; GtkTextRealIter *real; - g_return_if_fail(iter != NULL); + g_return_if_fail (iter != NULL); - real = gtk_text_iter_make_surreal(iter); + real = gtk_text_iter_make_surreal (iter); if (real == NULL) return; - check_invariants(iter); + check_invariants (iter); - line = gtk_text_btree_get_line(real->tree, line_number, &real_line); + line = gtk_text_btree_get_line (real->tree, line_number, &real_line); - iter_set_from_char_offset(real, line, 0); + 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); + check_invariants (iter); } void @@ -2360,10 +2566,10 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter, check_invariants(iter); current_line = real->line; - next_line = gtk_text_line_next_could_contain_tag(current_line, - real->tree, tag); + next_line = gtk_text_line_next_could_contain_tag (current_line, + real->tree, tag); - while (gtk_text_iter_forward_indexable_segment(iter)) + 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 @@ -2374,15 +2580,15 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter, if (next_line == NULL) { /* End of search. Set to end of buffer. */ - gtk_text_btree_get_last_iter(real->tree, iter); + gtk_text_btree_get_last_iter (real->tree, iter); return FALSE; } if (real->line != next_line) - iter_set_from_byte_offset(real, next_line, 0); + iter_set_from_byte_offset (real, next_line, 0); current_line = real->line; - next_line = gtk_text_line_next_could_contain_tag(current_line, + next_line = gtk_text_line_next_could_contain_tag (current_line, real->tree, tag); } @@ -2397,7 +2603,7 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter, } /* Check end iterator for tags */ - if (gtk_text_iter_toggles_tag(iter, 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. */ @@ -2409,12 +2615,103 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter, return FALSE; } +/** + * gtk_text_iter_backward_to_tag_toggle: + * @iter: a #GtkTextIter + * @tag: 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. + * + * Return value: 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; - g_warning("FIXME"); + 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 @@ -2437,9 +2734,9 @@ gtk_text_iter_forward_find_char (GtkTextIter *iter, g_return_val_if_fail(iter != NULL, FALSE); g_return_val_if_fail(pred != NULL, FALSE); - while (gtk_text_iter_next_char(iter)) + while (gtk_text_iter_next_char (iter)) { - if (matches_pred(iter, pred, user_data)) + if (matches_pred (iter, pred, user_data)) return TRUE; } @@ -2454,9 +2751,9 @@ gtk_text_iter_backward_find_char (GtkTextIter *iter, g_return_val_if_fail(iter != NULL, FALSE); g_return_val_if_fail(pred != NULL, FALSE); - while (gtk_text_iter_prev_char(iter)) + while (gtk_text_iter_prev_char (iter)) { - if (matches_pred(iter, pred, user_data)) + if (matches_pred (iter, pred, user_data)) return TRUE; } @@ -2640,7 +2937,8 @@ gtk_text_iter_forward_search (GtkTextIter *iter, * gtk_text_iter_get_text() is called repeatedly on * a single line. */ - if (lines_match (&search, (const gchar**)lines, visible_only, slice, &match)) + if (lines_match (&search, (const gchar**)lines, + visible_only, slice, &match)) { retval = TRUE; @@ -3062,8 +3360,8 @@ gtk_text_iter_check (const GtkTextIter *iter) 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); + &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"); @@ -3084,8 +3382,8 @@ gtk_text_iter_check (const GtkTextIter *iter) 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); + &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"); diff --git a/gtk/gtktextiterprivate.h b/gtk/gtktextiterprivate.h index 0d9104d3e1..bd6835a987 100644 --- a/gtk/gtktextiterprivate.h +++ b/gtk/gtktextiterprivate.h @@ -10,18 +10,15 @@ extern "C" { #include <gtk/gtktextiter.h> #include <gtk/gtktextbtree.h> -GtkTextLineSegment *gtk_text_iter_get_indexable_segment(const GtkTextIter *iter); -GtkTextLineSegment *gtk_text_iter_get_any_segment(const GtkTextIter *iter); +GtkTextLineSegment *gtk_text_iter_get_indexable_segment (const GtkTextIter *iter); +GtkTextLineSegment *gtk_text_iter_get_any_segment (const GtkTextIter *iter); +GtkTextLine * gtk_text_iter_get_text_line (const GtkTextIter *iter); +GtkTextBTree * gtk_text_iter_get_btree (const GtkTextIter *iter); +gboolean gtk_text_iter_forward_indexable_segment (GtkTextIter *iter); +gboolean gtk_text_iter_backward_indexable_segment (GtkTextIter *iter); +gint gtk_text_iter_get_segment_byte (const GtkTextIter *iter); +gint gtk_text_iter_get_segment_char (const GtkTextIter *iter); -GtkTextLine *gtk_text_iter_get_text_line(const GtkTextIter *iter); - -GtkTextBTree *gtk_text_iter_get_btree(const GtkTextIter *iter); - -gboolean gtk_text_iter_forward_indexable_segment(GtkTextIter *iter); -gboolean gtk_text_iter_backward_indexable_segment(GtkTextIter *iter); - -gint gtk_text_iter_get_segment_byte(const GtkTextIter *iter); -gint gtk_text_iter_get_segment_char(const GtkTextIter *iter); /* debug */ void gtk_text_iter_check(const GtkTextIter *iter); diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c index ff7da5e3b0..d4b57f973a 100644 --- a/gtk/gtktextlayout.c +++ b/gtk/gtktextlayout.c @@ -1204,8 +1204,6 @@ add_cursor (GtkTextLayout *layout, GtkTextLineSegment *seg, gint start) { - GtkTextIter selection_start, selection_end; - PangoRectangle strong_pos, weak_pos; GtkTextCursorDisplay *cursor; @@ -1214,7 +1212,8 @@ add_cursor (GtkTextLayout *layout, */ if (gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer), (GtkTextMark*)seg) && - (!layout->cursor_visible || gtk_text_buffer_get_selection_bounds (layout->buffer, &selection_start, &selection_end))) + (!layout->cursor_visible || + gtk_text_buffer_get_selection_bounds (layout->buffer, NULL, NULL))) return; pango_layout_get_cursor_pos (display->layout, start, &strong_pos, &weak_pos); @@ -1429,7 +1428,8 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, tmp_list2 = cursor_segs; while (tmp_list1) { - add_cursor (layout, display, tmp_list2->data, GPOINTER_TO_INT (tmp_list1->data)); + add_cursor (layout, display, tmp_list2->data, + GPOINTER_TO_INT (tmp_list1->data)); tmp_list1 = tmp_list1->next; tmp_list2 = tmp_list2->next; } diff --git a/gtk/gtktextlayout.h b/gtk/gtktextlayout.h index 5d812f1a99..61ad83dbf7 100644 --- a/gtk/gtktextlayout.h +++ b/gtk/gtktextlayout.h @@ -146,7 +146,7 @@ struct _GtkTextLineDisplay gint bottom_margin; gboolean size_only; - GtkTextLine *line; + GtkTextLine *line; }; extern PangoAttrType gtk_text_attr_appearance_type; diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 4bbd655b64..6e5b661084 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -530,7 +530,7 @@ gtk_text_view_class_init (GtkTextViewClass *klass) "delete", 2, GTK_TYPE_ENUM, GTK_DELETE_CHARS, GTK_TYPE_INT, 1); - + gtk_binding_entry_add_signal (binding_set, GDK_d, GDK_CONTROL_MASK, "delete", 2, GTK_TYPE_ENUM, GTK_DELETE_CHARS, @@ -2590,6 +2590,16 @@ gtk_text_view_delete (GtkTextView *text_view, break; case GTK_DELETE_PARAGRAPH_ENDS: + /* If we're already at a newline, we need to + * simply delete that newline, instead of + * moving to the next one. + */ + if (gtk_text_iter_get_char (&end) == '\n') + { + gtk_text_iter_next_char (&end); + --count; + } + while (count > 0) { if (!gtk_text_iter_forward_to_newline (&end)) @@ -3058,9 +3068,9 @@ gtk_text_view_destroy_layout (GtkTextView *text_view) */ static void -gtk_text_view_start_selection_dnd (GtkTextView *text_view, +gtk_text_view_start_selection_dnd (GtkTextView *text_view, const GtkTextIter *iter, - GdkEventMotion *event) + GdkEventMotion *event) { GdkDragContext *context; GtkTargetList *target_list; @@ -3079,7 +3089,8 @@ gtk_text_view_start_selection_dnd (GtkTextView *text_view, gtk_drag_set_icon_default (context); /* We're inside the selection, so start without being able - to accept the drag. */ + * to accept the drag. + */ gdk_drag_status (context, 0, event->time); gtk_text_mark_set_visible (text_view->dnd_mark, FALSE); } @@ -3201,7 +3212,8 @@ gtk_text_view_drag_motion (GtkWidget *widget, { if (gtk_text_iter_editable (&newplace, text_view->editable)) { - gtk_text_mark_set_visible (text_view->dnd_mark, text_view->cursor_visible); + gtk_text_mark_set_visible (text_view->dnd_mark, + text_view->cursor_visible); gdk_drag_status (context, context->suggested_action, time); } diff --git a/gtk/testtextbuffer.c b/gtk/testtextbuffer.c index 286f9b5fd7..437be8832f 100644 --- a/gtk/testtextbuffer.c +++ b/gtk/testtextbuffer.c @@ -16,14 +16,14 @@ main (int argc, char** argv) int n; gunichar ch; GtkTextIter start, end; - + gtk_init (&argc, &argv); /* Check UTF8 unknown char thing */ g_assert (g_utf8_strlen (gtk_text_unknown_char_utf8, 3) == 1); ch = g_utf8_get_char (gtk_text_unknown_char_utf8); g_assert (ch == gtk_text_unknown_char); - + /* First, we turn on btree debugging. */ gtk_debug_flags |= GTK_DEBUG_TEXT; @@ -31,18 +31,18 @@ main (int argc, char** argv) buffer = gtk_text_buffer_new (NULL); /* Check that buffer starts with one empty line and zero chars */ - + n = gtk_text_buffer_get_line_count (buffer); if (n != 1) g_error ("%d lines, expected 1", n); - + n = gtk_text_buffer_get_char_count (buffer); if (n != 1) g_error ("%d chars, expected 1", n); - + /* Run gruesome alien test suite on buffer */ run_tests (buffer); - + /* Put stuff in the buffer */ fill_buffer (buffer); @@ -59,7 +59,7 @@ main (int argc, char** argv) n = gtk_text_buffer_get_line_count (buffer); if (n != 1) g_error ("%d lines, expected 1", n); - + n = gtk_text_buffer_get_char_count (buffer); if (n != 1) g_error ("%d chars, expected 1", n); @@ -67,10 +67,165 @@ main (int argc, char** argv) run_tests (buffer); g_print ("All tests passed.\n"); - + return 0; } +static gint +count_toggles_at_iter (GtkTextIter *iter, + GtkTextTag *of_tag) +{ + GSList *tags; + GSList *tmp; + gint count = 0; + + /* get toggle-ons and toggle-offs */ + tags = gtk_text_iter_get_toggled_tags (iter, TRUE); + tags = g_slist_concat (tags, + gtk_text_iter_get_toggled_tags (iter, FALSE)); + + tmp = tags; + while (tmp != NULL) + { + if (of_tag == NULL) + ++count; + else if (of_tag == tmp->data) + ++count; + + tmp = g_slist_next (tmp); + } + + g_slist_free (tags); + + return count; +} + +static gint +count_toggles_in_buffer (GtkTextBuffer *buffer, + GtkTextTag *of_tag) +{ + GtkTextIter iter; + gint count = 0; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0); + do + { + count += count_toggles_at_iter (&iter, of_tag); + } + while (gtk_text_iter_next_char (&iter)); + + /* Do the end iterator, because forward_char won't return TRUE + * on it. + */ + count += count_toggles_at_iter (&iter, of_tag); + + return count; +} + +static void +check_specific_tag (GtkTextBuffer *buffer, + const gchar *tag_name) +{ + GtkTextIter iter; + GtkTextTag *tag; + gboolean state; + gint count; + gint buffer_count; + gint last_offset; + + tag = gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (buffer), + tag_name); + + buffer_count = count_toggles_in_buffer (buffer, tag); + + state = FALSE; + count = 0; + + last_offset = -1; + gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0); + if (gtk_text_iter_toggles_tag (&iter, tag) || + gtk_text_iter_forward_to_tag_toggle (&iter, tag)) + { + do + { + gint this_offset; + + ++count; + + this_offset = gtk_text_iter_get_offset (&iter); + + if (this_offset <= last_offset) + g_error ("forward_to_tag_toggle moved in wrong direction"); + + last_offset = this_offset; + + if (gtk_text_iter_begins_tag (&iter, tag)) + { + if (state) + g_error ("Tag %p is already on, and was toggled on?", tag); + state = TRUE; + } + else if (gtk_text_iter_ends_tag (&iter, tag)) + { + if (!state) + g_error ("Tag %p toggled off, but wasn't toggled on?", tag); + state = FALSE; + } + else + g_error ("forward_to_tag_toggle went to a location without a toggle"); + } + while (gtk_text_iter_forward_to_tag_toggle (&iter, tag)); + } + + if (count != buffer_count) + g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n", + buffer_count, count); + + state = FALSE; + count = 0; + + gtk_text_buffer_get_last_iter (buffer, &iter); + last_offset = gtk_text_iter_get_offset (&iter); + if (gtk_text_iter_toggles_tag (&iter, tag) || + gtk_text_iter_backward_to_tag_toggle (&iter, tag)) + { + do + { + gint this_offset; + + ++count; + + this_offset = gtk_text_iter_get_offset (&iter); + + if (this_offset >= last_offset) + g_error ("backward_to_tag_toggle moved in wrong direction"); + + last_offset = this_offset; + + if (gtk_text_iter_begins_tag (&iter, tag)) + { + if (!state) + g_error ("Tag %p wasn't on when we got to the on toggle going backward?", tag); + state = FALSE; + } + else if (gtk_text_iter_ends_tag (&iter, tag)) + { + if (state) + g_error ("Tag %p off toggle, but we were already inside a tag?", tag); + state = TRUE; + } + else + g_error ("backward_to_tag_toggle went to a location without a toggle"); + } + while (gtk_text_iter_backward_to_tag_toggle (&iter, tag)); + } + + if (count != buffer_count) + g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n", + buffer_count, count); + +} + static void run_tests (GtkTextBuffer *buffer) { @@ -81,9 +236,13 @@ run_tests (GtkTextBuffer *buffer) gint i, j; gint num_chars; GtkTextMark *bar_mark; + GtkTextTag *tag; + GHashTable *tag_states; + gint count; + gint buffer_count; gtk_text_buffer_get_bounds (buffer, &start, &end); - + /* Check that walking the tree via chars and via iterators produces * the same number of indexable locations. */ @@ -95,7 +254,7 @@ run_tests (GtkTextBuffer *buffer) { GtkTextIter current; GtkTextMark *foo_mark; - + gtk_text_buffer_get_iter_at_offset (buffer, ¤t, i); if (!gtk_text_iter_equal (&iter, ¤t)) @@ -119,11 +278,11 @@ run_tests (GtkTextBuffer *buffer) gtk_text_iter_spew (&mark, "mark"); g_error ("Mark not moved to the right place."); } - + foo_mark = gtk_text_buffer_create_mark (buffer, "foo", &iter, FALSE); gtk_text_buffer_get_iter_at_mark (buffer, &mark, foo_mark); gtk_text_buffer_delete_mark (buffer, foo_mark); - + if (!gtk_text_iter_equal (&iter, &mark)) { gtk_text_iter_spew (&iter, "iter"); @@ -134,34 +293,34 @@ run_tests (GtkTextBuffer *buffer) if (gtk_text_iter_is_last (&iter)) g_error ("iterators ran out before chars (offset %d of %d)", i, num_chars); - + gtk_text_iter_next_char (&iter); gtk_text_buffer_move_mark (buffer, bar_mark, &iter); - + ++i; } if (!gtk_text_iter_equal (&iter, &end)) g_error ("Iterating over all chars didn't end with the end iter"); - /* Do the tree-walk backward + /* Do the tree-walk backward */ num_chars = gtk_text_buffer_get_char_count (buffer); gtk_text_buffer_get_iter_at_offset (buffer, &iter, -1); gtk_text_buffer_move_mark (buffer, bar_mark, &iter); - + i = num_chars; if (!gtk_text_iter_equal (&iter, &end)) g_error ("iter at char -1 is not equal to the end iterator"); - + while (i >= 0) { GtkTextIter current; GtkTextMark *foo_mark; - + gtk_text_buffer_get_iter_at_offset (buffer, ¤t, i); if (!gtk_text_iter_equal (&iter, ¤t)) @@ -184,18 +343,18 @@ run_tests (GtkTextBuffer *buffer) gtk_text_iter_spew (&mark, "mark"); g_error ("Mark not moved to the right place."); } - + foo_mark = gtk_text_buffer_create_mark (buffer, "foo", &iter, FALSE); gtk_text_buffer_get_iter_at_mark (buffer, &mark, foo_mark); gtk_text_buffer_delete_mark (buffer, foo_mark); - + if (!gtk_text_iter_equal (&iter, &mark)) { gtk_text_iter_spew (&iter, "iter"); gtk_text_iter_spew (&mark, "mark"); g_error ("Mark not created in the right place."); } - + if (i > 0) { if (!gtk_text_iter_prev_char (&iter)) @@ -208,10 +367,10 @@ run_tests (GtkTextBuffer *buffer) if (gtk_text_iter_prev_char (&iter)) g_error ("went backward from 0?"); } - + --i; } - + if (!gtk_text_iter_equal (&iter, &start)) g_error ("Iterating backward over all chars didn't end with the start iter"); @@ -223,10 +382,169 @@ run_tests (GtkTextBuffer *buffer) gtk_text_buffer_get_iter_at_line (buffer, &iter, 0); while (gtk_text_iter_forward_line (&iter)) ++i; - + if (i != gtk_text_buffer_get_line_count (buffer)) g_error ("Counted %d lines, buffer has %d", i, gtk_text_buffer_get_line_count (buffer)); + + /* + * Check that moving over tag toggles thinks about working. + */ + + buffer_count = count_toggles_in_buffer (buffer, NULL); + + tag_states = g_hash_table_new (NULL, NULL); + count = 0; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0); + if (gtk_text_iter_toggles_tag (&iter, NULL) || + gtk_text_iter_forward_to_tag_toggle (&iter, NULL)) + { + do + { + GSList *tags; + GSList *tmp; + gboolean found_some = FALSE; + + /* get toggled-on tags */ + tags = gtk_text_iter_get_toggled_tags (&iter, TRUE); + + if (tags) + found_some = TRUE; + + tmp = tags; + while (tmp != NULL) + { + ++count; + + tag = tmp->data; + + if (g_hash_table_lookup (tag_states, tag)) + g_error ("Tag %p is already on, and was toggled on?", tag); + + g_hash_table_insert (tag_states, tag, GINT_TO_POINTER (TRUE)); + + tmp = g_slist_next (tmp); + } + + g_slist_free (tags); + + /* get toggled-off tags */ + tags = gtk_text_iter_get_toggled_tags (&iter, FALSE); + + if (tags) + found_some = TRUE; + + tmp = tags; + while (tmp != NULL) + { + ++count; + + tag = tmp->data; + + if (!g_hash_table_lookup (tag_states, tag)) + g_error ("Tag %p is already off, and was toggled off?", tag); + + g_hash_table_remove (tag_states, tag); + + tmp = g_slist_next (tmp); + } + + g_slist_free (tags); + + if (!found_some) + g_error ("No tags found going forward to tag toggle."); + + } + while (gtk_text_iter_forward_to_tag_toggle (&iter, NULL)); + } + + g_hash_table_destroy (tag_states); + + if (count != buffer_count) + g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n", + buffer_count, count); + + /* Go backward; here TRUE in the hash means we saw + * an off toggle last. + */ + + tag_states = g_hash_table_new (NULL, NULL); + count = 0; + + gtk_text_buffer_get_last_iter (buffer, &iter); + if (gtk_text_iter_toggles_tag (&iter, NULL) || + gtk_text_iter_backward_to_tag_toggle (&iter, NULL)) + { + do + { + GSList *tags; + GSList *tmp; + gboolean found_some = FALSE; + + /* get toggled-off tags */ + tags = gtk_text_iter_get_toggled_tags (&iter, FALSE); + + if (tags) + found_some = TRUE; + + tmp = tags; + while (tmp != NULL) + { + ++count; + + tag = tmp->data; + + if (g_hash_table_lookup (tag_states, tag)) + g_error ("Tag %p has two off-toggles in a row?", tag); + + g_hash_table_insert (tag_states, tag, GINT_TO_POINTER (TRUE)); + + tmp = g_slist_next (tmp); + } + + g_slist_free (tags); + + /* get toggled-on tags */ + tags = gtk_text_iter_get_toggled_tags (&iter, TRUE); + + if (tags) + found_some = TRUE; + + tmp = tags; + while (tmp != NULL) + { + ++count; + + tag = tmp->data; + + if (!g_hash_table_lookup (tag_states, tag)) + g_error ("Tag %p was toggled on, but saw no off-toggle?", tag); + + g_hash_table_remove (tag_states, tag); + + tmp = g_slist_next (tmp); + } + + g_slist_free (tags); + + if (!found_some) + g_error ("No tags found going backward to tag toggle."); + } + while (gtk_text_iter_backward_to_tag_toggle (&iter, NULL)); + } + + g_hash_table_destroy (tag_states); + + if (count != buffer_count) + g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n", + buffer_count, count); + + check_specific_tag (buffer, "fg_red"); + check_specific_tag (buffer, "bg_green"); + check_specific_tag (buffer, "front_tag"); + check_specific_tag (buffer, "center_tag"); + check_specific_tag (buffer, "end_tag"); } @@ -264,7 +582,7 @@ fill_buffer (GtkTextBuffer *buffer) GtkTextIter iter2; GdkPixbuf *pixbuf; int i; - + tag = gtk_text_buffer_create_tag (buffer, "fg_blue"); color.red = color.green = 0; @@ -297,49 +615,49 @@ fill_buffer (GtkTextBuffer *buffer) NULL); pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm); - + g_assert (pixbuf != NULL); - + i = 0; while (i < 10) { gchar *str; gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0); - + gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf); gtk_text_buffer_get_iter_at_offset (buffer, &iter, 1); - + gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf); - + str = g_strdup_printf ("%d Hello World!\nwoo woo woo woo woo woo woo woo\n", i); - + gtk_text_buffer_insert (buffer, &iter, str, -1); g_free (str); - + gtk_text_buffer_insert (buffer, &iter, "(Hello World!)\nfoo foo Hello this is some text we are using to text word wrap. It has punctuation! gee; blah - hmm, great.\nnew line\n\n" /* This is UTF8 stuff, Emacs doesn't really know how to display it */ "Spanish (Español) ¡Hola! / French (Français) Bonjour, Salut / German (Deutsch Süd) Grüß Gott (testing Latin-1 chars encoded in UTF8)\nThai (we can't display this, just making sure we don't crash) (ภาษาไทย) สวัสดีครับ, สวัสดีค่ะ\n", - -1); - + -1); + gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf); gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf); - + gtk_text_buffer_get_iter_at_offset (buffer, &iter, 4); - + gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf); gtk_text_buffer_get_iter_at_offset (buffer, &iter, 7); - + gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf); gtk_text_buffer_get_iter_at_offset (buffer, &iter, 8); - + gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf); gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 8); @@ -350,40 +668,66 @@ fill_buffer (GtkTextBuffer *buffer) gtk_text_iter_forward_chars (&iter, 7); gtk_text_iter_forward_chars (&iter2, 10); - + gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2); gtk_text_iter_forward_chars (&iter, 12); gtk_text_iter_forward_chars (&iter2, 10); - + gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2); gtk_text_iter_forward_chars (&iter, 10); gtk_text_iter_forward_chars (&iter2, 15); - + gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2); - gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2); + gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2); gtk_text_iter_forward_chars (&iter, 20); gtk_text_iter_forward_chars (&iter2, 20); - + gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2); - gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2); + gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2); gtk_text_iter_backward_chars (&iter, 25); gtk_text_iter_forward_chars (&iter2, 5); - + gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2); - gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2); + gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2); gtk_text_iter_forward_chars (&iter, 15); gtk_text_iter_backward_chars (&iter2, 10); gtk_text_buffer_remove_tag_by_name (buffer, "fg_red", &iter, &iter2); - gtk_text_buffer_remove_tag_by_name (buffer, "fg_blue", &iter, &iter2); - + gtk_text_buffer_remove_tag_by_name (buffer, "fg_blue", &iter, &iter2); + ++i; } + /* Put in tags that are just at the beginning, and just near the end, + * and just near the middle. + */ + tag = gtk_text_buffer_create_tag (buffer, "front_tag"); + gtk_text_buffer_get_iter_at_offset (buffer, &iter, 3); + gtk_text_buffer_get_iter_at_offset (buffer, &iter2, 300); + + gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2); + + tag = gtk_text_buffer_create_tag (buffer, "end_tag"); + gtk_text_buffer_get_last_iter (buffer, &iter2); + gtk_text_iter_backward_chars (&iter2, 12); + iter = iter2; + gtk_text_iter_backward_chars (&iter, 157); + + gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2); + + tag = gtk_text_buffer_create_tag (buffer, "center_tag"); + gtk_text_buffer_get_iter_at_offset (buffer, &iter, + gtk_text_buffer_get_char_count (buffer)/2); + gtk_text_iter_backward_chars (&iter, 37); + iter2 = iter; + gtk_text_iter_forward_chars (&iter2, 57); + + gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2); + gdk_pixbuf_unref (pixbuf); } |