/* Height and width of this node */ int height; signedint width : 24;
/* boolean indicating whether the lines below this node are in need of validation. *However,width/heightshouldalwaysrepresentthecurrenttotalwidthand *maxheightforlinesbelowthisnode;thevalidflagindicateswhetherthe *width/heightonthelinesneedsrecomputing,notwhetherthetotals *needrecomputing.
*/
guint valid : 8; /* Actually a boolean */
};
typedefstruct Summary {
GtkTextTagInfo *info; /* Handle for tag. */ int toggle_count; /* Number of transitions into or *outofthistagthatoccurin
* the subtree rooted at this node. */ struct Summary *next; /* Next in list of all tags for same
* node, or NULL if at end of list. */
} Summary;
struct _GtkTextBTreeNode {
GtkTextBTreeNode *parent; /* Pointer to parent node, or NULL if
* this is the root. */
GtkTextBTreeNode *next; /* Next in list of siblings with the *sameparentnode,orNULLforend
* of list. */
Summary *summary; /* First in malloc-ed list of info *abouttagsinthissubtree(NULLif
* no tag info in the subtree). */ int level; /* Level of this node in the B-tree. *0referstothebottomofthetree
* (children are lines, not nodes). */ int num_lines; /* Total number of lines (leaves) in
* the subtree rooted here. */ int num_chars; /* Number of chars below here */ int num_children; /* Number of children of this node. */ union { /* First in linked list of children. */ struct _GtkTextBTreeNode *node; /* Used if level > 0. */
GtkTextLine *line; /* Used if level == 0. */
} children;
/* Incremented when a segment with a byte size > 0 *isaddedtoorremovedfromthetree(i.e.the *lengthofalinemayhavechanged,andlinesmay *havebeenaddedorremoved).Thisinvalidates *alloutstandingiterators.
*/
guint chars_changed_stamp; /* Incremented when any segments are added or deleted; *thismakesoutstandingiteratorsrecalculatetheir *pointed-tosegmentandsegmentoffset.
*/
guint segments_changed_stamp;
/* Cache the last line in the buffer */
GtkTextLine *last_line;
guint last_line_stamp;
/* Cache the next-to-last line in the buffer, *containingtheenditerator
*/
GtkTextLine *end_iter_line;
GtkTextLineSegment *end_iter_segment; int end_iter_segment_byte_index; int end_iter_segment_char_offset;
guint end_iter_line_stamp;
guint end_iter_segment_stamp;
/* Tk used MAX of 12 and MIN of 6. This makes the tree wide and shallow.Itappearstobefastertolocateaparticularlinenumber ifthetreeisnarrowanddeep,sinceitismorefinelysorted.I guessthismayincreasememoryusethough,andmakeitslowerto walkthetreeinorder,orlocateaparticularbyteindex(which isdonebywalkingthetreeinorder).
/* Set these to values that are unlikely to be found *inrandommemorygarbage,andalsoavoid *duplicatesbetweentreeinstances.
*/
tree->chars_changed_stamp = g_random_int ();
tree->segments_changed_stamp = g_random_int ();
/* We don't ref the buffer, since the buffer owns us; *we'dhavesomecircularityissues.Thebufferalways *lastslongerthantheBTree
*/
tree->buffer = buffer;
/* Resolve the strong bidi direction for all lines between *startandend.
*/
start_line = _gtk_text_iter_get_text_line (start);
start_line_prev = _gtk_text_line_previous (start_line);
end_line = _gtk_text_iter_get_text_line (end);
end_line_next = _gtk_text_line_next (end_line);
line = start_line; while (line && line != end_line_next)
{ /* Loop through the segments and search for a strong character
*/
GtkTextLineSegment *seg = line->segments;
line->dir_strong = PANGO_DIRECTION_NEUTRAL;
while (seg)
{ if (seg->type == >k_text_char_type && seg->byte_count > 0)
{
PangoDirection pango_dir;
if (pango_dir != PANGO_DIRECTION_NEUTRAL)
{
line->dir_strong = pango_dir; break;
}
}
seg = seg->next;
}
line = _gtk_text_line_next (line);
}
/* Sweep forward */
/* The variable dir_above_propagated contains the forward propagated *directionbeforestart.Itisneutralifstartisinthebeginning *ofthebuffer.
*/
dir_above_propagated = PANGO_DIRECTION_NEUTRAL; if (start_line_prev)
dir_above_propagated = start_line_prev->dir_propagated_forward;
/* Loop forward and propagate the direction of each paragraph *toallneutrallines.
*/
line = start_line;
last_strong = dir_above_propagated; while (line != end_line_next)
{ if (line->dir_strong != PANGO_DIRECTION_NEUTRAL)
last_strong = line->dir_strong;
line->dir_propagated_forward = last_strong;
line = _gtk_text_line_next (line);
}
/* Continue propagating as long as the previous resolved forward *isdifferentfromlast_strong.
*/
{
GtkTextIter end_propagate;
line = _gtk_text_line_next(line); if (!line)
{
line = prev; break;
}
}
/* The last line to invalidate is the last line before the *linewiththestrongcharacter.Orincaseoftheendofthe *buffer,thelastlineofthebuffer.(Thereseemstobean *extra"virtual"lastlineinthebufferthatmustnotbeused *calling_gtk_text_btree_get_iter_at_line(causescrash).Thusthe *_gtk_text_line_previousisokinthatcaseaswell.)
*/
line = _gtk_text_line_previous (line);
_gtk_text_btree_get_iter_at_line (tree, &end_propagate, line, 0);
_gtk_text_btree_invalidate_region (tree, end, &end_propagate, FALSE);
}
/* Sweep backward */
/* The variable dir_below_propagated contains the backward propagated *directionafterend.Itisneutralifendisattheendof *thebuffer.
*/
dir_below_propagated = PANGO_DIRECTION_NEUTRAL; if (end_line_next)
dir_below_propagated = end_line_next->dir_propagated_back;
/* Loop backward and propagate the direction of each paragraph *toallneutrallines.
*/
line = end_line;
last_strong = dir_below_propagated; while (line != start_line_prev)
{ if (line->dir_strong != PANGO_DIRECTION_NEUTRAL)
last_strong = line->dir_strong;
line->dir_propagated_back = last_strong;
line = _gtk_text_line_previous (line);
}
/* Continue propagating as long as the resolved backward dir *isdifferentfromlast_strong.
*/
{
GtkTextIter start_propagate;
line = _gtk_text_line_previous (line); if (!line)
{
line = prev; break;
}
}
/* We only need to invalidate for backwards propagation if the *lineweendedupondidn'tgetadirectionfromforwards *propagation.
*/ if (line && line->dir_propagated_forward == PANGO_DIRECTION_NEUTRAL)
{
_gtk_text_btree_get_iter_at_line (tree, &start_propagate, line, 0);
_gtk_text_btree_invalidate_region (tree, &start_propagate, start, FALSE);
}
}
}
void
_gtk_text_btree_delete (GtkTextIter *start,
GtkTextIter *end)
{
GtkTextLineSegment *prev_seg; /* The segment just before the start
* of the deletion range. */
GtkTextLineSegment *last_seg; /* The segment just after the end
* of the deletion range. */
GtkTextLineSegment *seg, *next, *next2;
GtkTextLine *curline;
GtkTextBTreeNode *curnode, *node;
GtkTextBTree *tree;
GtkTextLine *start_line;
GtkTextLine *end_line;
GtkTextLine *line;
GtkTextLine *deleted_lines = NULL; /* List of lines we've deleted */ int start_byte_offset;
if (GTK_DEBUG_CHECK (TEXT))
_gtk_text_btree_check (tree);
/* Broadcast the need for redisplay before we break the iterators */
DV (g_print ("invalidating due to deleting some text (%s)\n", G_STRLOC));
_gtk_text_btree_invalidate_region (tree, start, end, FALSE);
/* Save the byte offset so we can reset the iterators */
start_byte_offset = gtk_text_iter_get_line_index (start);
if (prev_seg == NULL)
{
seg->next = start_line->segments;
start_line->segments = seg;
} elseif (prev_seg->next &&
prev_seg->next != last_seg &&
seg->type == >k_text_toggle_off_type &&
prev_seg->next->type == >k_text_toggle_on_type &&
seg->body.toggle.info == prev_seg->next->body.toggle.info)
{ /* Try to match an off toggle with the matching on toggle *ifitimmediatelyfollows.Thisisacommoncase,and *handlingitherepreventsquadraticblowupin *cleanup_line()below.Seebug317125.
*/
next2 = prev_seg->next->next;
_gtk_toggle_segment_free (prev_seg->next);
prev_seg->next = next2;
_gtk_toggle_segment_free (seg);
seg = NULL;
} else
{
seg->next = prev_seg->next;
prev_seg->next = seg;
}
if (seg && seg->type->leftGravity)
{
prev_seg = seg;
}
} else
{ /* Segment is gone. Decrement the char count of the node and
all its parents. */ for (node = curnode; node != NULL;
node = node->parent)
{
node->num_chars -= char_count;
}
}
if (start_line != end_line)
{
BTreeView *view;
GtkTextBTreeNode *ancestor_node;
GtkTextLine *prevline; int chars_moved;
/* last_seg was appended to start_line up at the top of this function */
chars_moved = 0; for (seg = last_seg; seg != NULL;
seg = seg->next)
{
chars_moved += seg->char_count; if (seg->type->lineChangeFunc != NULL)
{
(*seg->type->lineChangeFunc)(seg, end_line);
}
}
/* We now fix up the per-view aggregates. We add all the height and *widthforthedeletedlinestothestartline,sothatwhenrevalidation *occurs,thecorrectchangeinsizeisseen.
*/
ancestor_node = gtk_text_btree_node_common_parent (curnode, start_line->parent);
view = tree->views; while (view)
{
GtkTextLineData *ld;
int deleted_width = 0; int deleted_height = 0;
line = deleted_lines; while (line)
{
GtkTextLine *next_line = line->next;
ld = _gtk_text_line_get_data (line, view->view_id);
if (ld)
{
deleted_width = MAX (deleted_width, ld->width);
deleted_height += ld->height;
}
void
_gtk_text_btree_insert (GtkTextIter *iter, constchar *text, int len)
{
GtkTextLineSegment *prev_seg; /* The segment just before the first *newsegment(NULLmeansnewsegment
* is at beginning of line). */
GtkTextLineSegment *cur_seg; /* Current segment; new characters *areinsertedjustafterthisone. *NULLmeansinsertatbeginningof
* line. */
GtkTextLine *line; /* Current line (new segments are
* added to this line). */
GtkTextLineSegment *seg;
GtkTextLine *newline; int chunk_len; /* # characters in current chunk. */ int sol; /* start of line */ int eol; /* Pointer to character just after last *oneincurrentchunk.
*/ int delim; /* index of paragraph delimiter */ int line_count_delta; /* Counts change to total number of *linesinfile.
*/
int char_count_delta; /* change to number of chars */
GtkTextBTree *tree; int start_byte_index;
GtkTextLine *start_line;
/* Invalidate our region, and reset the iterator the user
passed in to point to the end of the inserted text. */
{
GtkTextIter start;
GtkTextIter end;
_gtk_text_btree_get_iter_at_line (tree,
&start,
start_line,
start_byte_index);
end = start;
/* We could almost certainly be more efficient here bysavingtheinformationfromtheinsertionloop
above. FIXME */
gtk_text_iter_forward_chars (&end, char_count_delta);
DV (g_print ("invalidating due to inserting some text (%s)\n", G_STRLOC));
_gtk_text_btree_invalidate_region (tree, &start, &end, FALSE);
static GtkTextLine*
find_line_by_y (GtkTextBTree *tree, BTreeView *view,
GtkTextBTreeNode *node, int y, int *line_top,
GtkTextLine *last_line)
{ int current_y = 0;
if (GTK_DEBUG_CHECK (TEXT))
_gtk_text_btree_check (tree);
if (node->level == 0)
{
GtkTextLine *line;
line = node->children.line;
while (line != NULL && line != last_line)
{
GtkTextLineData *ld;
line = find_line_by_y (tree, view, tree->root_node, ypixel, &line_top,
last_line);
if (line_top_out)
*line_top_out = line_top;
return line;
}
staticint
find_line_top_in_line_list (GtkTextBTree *tree,
BTreeView *view,
GtkTextLine *line,
GtkTextLine *target_line, int y)
{ while (line != NULL)
{
GtkTextLineData *ld;
if (line == target_line) return y;
ld = _gtk_text_line_get_data (line, view->view_id); if (ld)
y += ld->height;
line = line->next;
}
g_assert_not_reached (); /* If we get here, our targetlinedidn'texist
under its parent node */ return0;
}
int
_gtk_text_btree_find_line_top (GtkTextBTree *tree,
GtkTextLine *target_line,
gpointer view_id)
{ int y = 0;
BTreeView *view;
GtkTextBTreeNode *node;
GtkTextBTreeNode *nodes[64]; int tos = 0;
g_assert (tos > 0); /* not at level 0 */
target_node = nodes[tos - 1];
child = node->children.node;
while (child != NULL)
{ int width; int height;
if (child == target_node) break; else
{
gtk_text_btree_node_get_size (child, view->view_id,
&width, &height);
y += height;
}
child = child->next;
}
g_assert (child != NULL); /* should have broken out before we
ran out of nodes */
}
tos--;
}
g_assert_not_reached (); /* we return when we find the target line */ return0;
}
/* The last line in the buffer has identity values for the per-view *datasothatwecanavoidspecialcasechecksforitinalarge *numberofloops
*/
last_line = get_last_line (tree);
while (view != NULL)
{ if (view->view_id == view_id) break;
view = view->next;
}
g_return_if_fail (view != NULL);
if (view->next)
view->next->prev = view->prev;
if (view->prev)
view->prev->next = view->next;
if (view == tree->views)
tree->views = view->next;
/* Remove the line data for the last line which we added ourselves. *(Dothisfirst,sothatwedon'ttrytocalltheview'slinedatadestructoronit.)
*/
last_line = get_last_line (tree);
line_data = _gtk_text_line_remove_data (last_line, view_id);
g_free (line_data);
staticvoid
queue_tag_redisplay (GtkTextBTree *tree,
GtkTextTag *tag, const GtkTextIter *start, const GtkTextIter *end)
{ if (_gtk_text_tag_affects_size (tag))
{
DV (g_print ("invalidating due to size-affecting tag (%s)\n", G_STRLOC));
_gtk_text_btree_invalidate_region (tree, start, end, FALSE);
} elseif (_gtk_text_tag_affects_nonsize_appearance (tag))
{ /* We only need to queue a redraw, not a relayout */
redisplay_region (tree, start, end, FALSE);
}
/* We don't need to do anything if the tag doesn't affect display */
}
/* Find all tag toggles in the region; we are going to delete them. Weneedtofindtheminadvance,because forward_find_tag_toggle()won'tworkoncewestartplayingaround
with the tree. */
stack = iter_stack_new ();
iter = start;
/* forward_to_tag_toggle() skips a toggle at the start iterator, *whichisdeliberate-wedon'twanttodeleteatoggleatthe *start.
*/ while (gtk_text_iter_forward_to_tag_toggle (&iter, tag))
{ if (gtk_text_iter_compare (&iter, &end) >= 0) break; else
iter_stack_push (stack, &iter);
}
/* We need to traverse the toggles in order. */
iter_stack_invert (stack);
toggled_on = gtk_text_iter_has_tag (&start, tag); if ( (add && !toggled_on) ||
(!add && toggled_on) )
{ /* This could create a second toggle at the start position;
cleanup_line () will remove it if so. */
seg = _gtk_toggle_segment_new (info, add);
/* cleanup_line adds the new toggle to the node counts. */ #if0
printf ("added toggle at start\n"); #endif /* we should probably call segments_changed, but in theory anystill-cachedsegmentsintheitersweareaboutto usearestillvalid,sincethey'reinfront
of this spot. */
}
/* toggled_on now reflects the toggle state _just before_ the enditerator.Theenditeratorcouldalreadyhaveatoggle
on or a toggle off. */ if ( (add && !toggled_on) ||
(!add && toggled_on) )
{ /* This could create a second toggle at the start position;
cleanup_line () will remove it if so. */
seg = _gtk_toggle_segment_new (info, !add);
prev = gtk_text_line_segment_split (&end); if (prev == NULL)
{
seg->next = end_line->segments;
end_line->segments = seg;
} else
{
seg->next = prev->next;
prev->next = seg;
} /* cleanup_line adds the new toggle to the node counts. */
g_assert (seg->body.toggle.inNodeCounts == FALSE); #if0
printf ("added toggle at end\n"); #endif
}
cleanup_line (cleanupline); if (cleanupline != end_line)
{
cleanup_line (end_line);
}
segments_changed (tree);
queue_tag_redisplay (tree, tag, &start, &end);
if (GTK_DEBUG_CHECK (TEXT))
_gtk_text_btree_check (tree);
}
/* *"Getters"
*/
static GtkTextLine*
get_line_internal (GtkTextBTree *tree, int line_number, int *real_line_number,
gboolean include_last)
{
GtkTextBTreeNode *node;
GtkTextLine *line; int lines_left; int line_count;
line_count = _gtk_text_btree_line_count (tree); if (!include_last)
line_count -= 1;
GtkTextLine*
_gtk_text_btree_get_line (GtkTextBTree *tree, int line_number, int *real_line_number)
{ return get_line_internal (tree, line_number, real_line_number, TRUE);
}
GtkTextLine*
_gtk_text_btree_get_line_no_last (GtkTextBTree *tree, int line_number, int *real_line_number)
{ return get_line_internal (tree, line_number, real_line_number, FALSE);
}
GtkTextLine*
_gtk_text_btree_get_line_at_char (GtkTextBTree *tree, int char_index, int *line_start_index, int *real_char_index)
{
GtkTextBTreeNode *node;
GtkTextLine *line;
GtkTextLineSegment *seg; int chars_left; int chars_in_line;
node = tree->root_node;
/* Clamp to valid indexes (-1 is magic for "highest index"), *node->num_charsincludesthetwonewlinesthataren'treally *inthebuffer.
*/ if (char_index < 0 || char_index >= (node->num_chars - 1))
{
char_index = node->num_chars - 2;
}
chars_in_line = 0;
seg = NULL; for (line = node->children.line; line != NULL; line = line->next)
{
seg = line->segments; while (seg != NULL)
{ if (chars_in_line + seg->char_count > chars_left) goto found; /* found our line/segment */
chars_in_line += seg->char_count;
seg = seg->next;
}
chars_left -= chars_in_line;
chars_in_line = 0;
seg = NULL;
}
found:
g_assert (line != NULL); /* hosage, ran out of lines */
g_assert (seg != NULL);
seg = _gtk_text_iter_get_indexable_segment (start);
end_seg = _gtk_text_iter_get_indexable_segment (end);
if (seg->type == >k_text_char_type)
{
gboolean copy = TRUE; int copy_bytes = 0; int copy_start = 0;
/* Don't copy if we're invisible; segments are invisible/not
as a whole, no need to check each char */ if (!include_hidden &&
_gtk_text_btree_char_is_invisible (start))
{
copy = FALSE; /* printf (" <invisible>\n"); */
}
int
_gtk_text_btree_line_count (GtkTextBTree *tree)
{ /* Subtract bogus line at the end; we return a count
of usable lines. */ return tree->root_node->num_lines - 1;
}
int
_gtk_text_btree_char_count (GtkTextBTree *tree)
{ /* Exclude newline in bogus last line and the *oneinthelastlinethatisaftertheenditerator
*/ return tree->root_node->num_chars - 2;
}
gboolean
_gtk_text_btree_char_is_invisible (const GtkTextIter *iter)
{
gboolean invisible = FALSE; /* if nobody says otherwise, it's visible */ int *tagCnts;
GtkTextTag **tags; int numTags;
GtkTextBTreeNode *node;
GtkTextLine *siblingline;
GtkTextLineSegment *seg;
GtkTextTag *tag; int i, index;
GtkTextLine *line;
GtkTextBTree *tree; int byte_index;
tree = _gtk_text_iter_get_btree (iter);
/* Short-circuit if we've never seen a visibility tag within the *tagtable(meaningeverythingmustbevisible).
*/ if G_LIKELY (!_gtk_text_tag_table_affects_visibility (tree->table)) returnFALSE;
for (index = 0, seg = line->segments;
(index + seg->byte_count) <= byte_index; /* segfault here means invalid index */
index += seg->byte_count, seg = seg->next)
{ if ((seg->type == >k_text_toggle_on_type)
|| (seg->type == >k_text_toggle_off_type))
{
tag = seg->body.toggle.info->tag; if (tag->priv->invisible_set)
{
tags[tag->priv->priority] = tag;
tagCnts[tag->priv->priority]++;
}
}
}
for (i = numTags-1; i >=0; i--)
{ if (tagCnts[i] & 1)
{ /* FIXME not sure this should be if 0 */ #if0 #ifndef ALWAYS_SHOW_SELECTION /* who would make the selection invisible? */ if ((tag == tkxt->seltag)
&& !(tkxt->flags & GOT_FOCUS))
{ continue;
} #endif #endif
invisible = tags[i]->priv->values->invisible; break;
}
}
DV (g_print ("invalidating due to moving visible mark (%s)\n", G_STRLOC));
cursor_only = mark == mark->body.mark.tree->insert_mark->segment;
_gtk_text_btree_invalidate_region (mark->body.mark.tree, &iter, &end, cursor_only);
}
if (existing_mark)
{ if (gtk_text_mark_get_buffer (existing_mark) != NULL)
mark = existing_mark->segment; else
mark = NULL;
} elseif (name != NULL)
mark = g_hash_table_lookup (tree->mark_table,
name); else
mark = NULL;
if (should_exist && mark == NULL)
{
g_warning ("No mark '%s' exists!", name); return NULL;
}
/* OK if !should_exist and it does already exist, in that case *wejustmoveit.
*/
iter = *where;
if (GTK_DEBUG_CHECK (TEXT))
_gtk_text_iter_check (&iter);
if (mark != NULL)
{ if (redraw_selections &&
(mark == tree->insert_mark->segment ||
mark == tree->selection_bound_mark->segment))
{
GtkTextIter old_pos;
if (mark->body.mark.visible)
{
ensure_not_off_end (tree, mark, &iter);
}
/* Redraw the mark's old location. */
redisplay_mark_if_visible (mark);
/* Unlink mark from its current location.
This could hose our iterator... */
gtk_text_btree_unlink_segment (tree, mark,
mark->body.mark.line);
mark->body.mark.line = _gtk_text_iter_get_text_line (&iter);
g_assert (mark->body.mark.line == _gtk_text_iter_get_text_line (&iter));
segments_changed (tree); /* make sure the iterator recomputes its
segment */
} else
{ if (existing_mark)
g_object_ref (existing_mark); else
existing_mark = gtk_text_mark_new (name, left_gravity);
mark = existing_mark->segment;
_gtk_mark_segment_set_tree (mark, tree);
/* Check if it's no-op since gtk_text_buffer_place_cursor()
* also calls this, and this will redraw the cursor line. */ if (!gtk_text_iter_equal (&old_ins, ins) ||
!gtk_text_iter_equal (&old_bound, bound))
{
redisplay_region (tree, &old_ins, &old_bound, TRUE);
/* Move insert AND selection_bound before we redisplay */
real_set_mark (tree, tree->insert_mark, "insert", FALSE, ins, TRUE, FALSE);
real_set_mark (tree, tree->selection_bound_mark, "selection_bound", FALSE, bound, TRUE, FALSE);
if (tag != NULL)
{
info = gtk_text_btree_get_existing_tag_info (tree, tag);
if (info == NULL) return NULL;
if (info->tag_root == NULL) return NULL;
node = info->tag_root;
/* We know the tag root has instances of the given
tag below it */
continue_outer_loop:
g_assert (node != NULL); while (node->level > 0)
{
g_assert (node != NULL); /* Failure probably means bad tag summaries. */
node = node->children.node; while (node != NULL)
{ if (gtk_text_btree_node_has_tag (node, tag)) goto continue_outer_loop;
node = node->next;
}
g_assert (node != NULL);
}
g_assert (node != NULL); /* The tag summaries said some node had
tag toggles... */
g_assert (node->level == 0);
return node->children.line;
} else
{ /* Looking for any tag at all (tag == NULL). Unfortunatelythiscan'tbedoneinasimpleandefficientway rightnow;soI'mjustgoingtoreturnthe
first line in the btree. FIXME */ return _gtk_text_btree_get_line (tree, 0, NULL);
}
}
/* FIXME this function is far too slow, for no good reason. */
gboolean
_gtk_text_line_char_has_tag (GtkTextLine *line,
GtkTextBTree *tree, int char_in_line,
GtkTextTag *tag)
{
GtkTextLineSegment *toggle_seg;
seg = tree->end_iter_line->segments; while (seg != NULL)
{ if (seg->char_count > 0)
last_with_chars = seg;
seg = seg->next;
}
g_assert (last_with_chars);
tree->end_iter_segment = last_with_chars;
/* We know the last char in the last line is '\n' */
tree->end_iter_segment_byte_index = last_with_chars->byte_count - 1;
tree->end_iter_segment_char_offset = last_with_chars->char_count - 1;
/* If we were on the end iter line, we can't go to *thelastline
*/ if (next && next->next == NULL && /* these checks are optimization only */
_gtk_text_line_next (next) == NULL) return NULL;
/* *FindthelineunderthisGtkTextBTreeNodejustbeforethestartingline.
*/
prev = line->parent->children.line; /* First line at leaf */ while (prev != line)
{ if (prev->next == line) return prev;
prev = prev->next;
if (prev == NULL)
g_error ("gtk_text_btree_previous_line ran out of lines");
}
int
_gtk_text_line_char_count (GtkTextLine *line)
{
GtkTextLineSegment *seg; int size;
size = 0;
seg = line->segments; while (seg != NULL)
{
size += seg->char_count;
seg = seg->next;
} return size;
}
int
_gtk_text_line_byte_count (GtkTextLine *line)
{
GtkTextLineSegment *seg; int size;
size = 0;
seg = line->segments; while (seg != NULL)
{
size += seg->byte_count;
seg = seg->next;
}
return size;
}
int
_gtk_text_line_char_index (GtkTextLine *target_line)
{
GtkTextBTreeNode *node_stack[64];
GtkTextBTreeNode *iter;
GtkTextLine *line; int num_chars; int tos = 0;
/* Push all our parent nodes onto a stack */
iter = target_line->parent;
g_assert (iter != NULL);
while (iter != NULL && tos < 64)
{
node_stack[tos++] = iter;
iter = iter->parent;
}
tos--;
/* Check that we have the root node on top of the stack. */
g_assert (node_stack[tos] != NULL &&
node_stack[tos]->parent == NULL);
/* Add up chars in all nodes before the nodes in our stack.
*/
num_chars = 0; while (tos >= 0)
{
GtkTextBTreeNode *child_iter;
iter = node_stack[tos];
if (iter->level == 0)
{ /* stack should be empty when we're on the last node */
g_assert (tos == 0); break; /* Our children are now lines */
}
tos--;
/* Add up chars before us in the tree */
child_iter = iter->children.node; while (child_iter != node_stack[tos])
{
g_assert (child_iter != NULL);
/* Since we don't store char counts in lines, only in segments, we havetoiterateoverthelinesaddingupsegmentcharcounts
until we find our line. */
line = iter->children.line; while (line != target_line)
{
g_assert (line != NULL);
num_chars += _gtk_text_line_char_count (line);
line = line->next;
}
g_assert (line == target_line);
return num_chars;
}
GtkTextLineSegment*
_gtk_text_line_byte_to_segment (GtkTextLine *line, int byte_offset, int *seg_offset)
{
GtkTextLineSegment *seg; int offset;
g_return_val_if_fail (line != NULL, NULL);
offset = byte_offset;
seg = line->segments;
while (offset >= seg->byte_count)
{
offset -= seg->byte_count;
seg = seg->next;
g_assert (seg != NULL); /* means an invalid byte index */
}
if (seg_offset)
*seg_offset = offset;
return seg;
}
GtkTextLineSegment*
_gtk_text_line_char_to_segment (GtkTextLine *line, int char_offset, int *seg_offset)
{
GtkTextLineSegment *seg; int offset;
g_return_val_if_fail (line != NULL, NULL);
offset = char_offset;
seg = line->segments;
while (offset >= seg->char_count)
{
offset -= seg->char_count;
seg = seg->next;
g_assert (seg != NULL); /* means an invalid char index */
}
if (seg_offset)
*seg_offset = offset;
return seg;
}
GtkTextLineSegment*
_gtk_text_line_byte_to_any_segment (GtkTextLine *line, int byte_offset, int *seg_offset)
{
GtkTextLineSegment *seg; int offset;
g_return_val_if_fail (line != NULL, NULL);
offset = byte_offset;
seg = line->segments;
while (offset > 0 && offset >= seg->byte_count)
{
offset -= seg->byte_count;
seg = seg->next;
g_assert (seg != NULL); /* means an invalid byte index */
}
if (seg_offset)
*seg_offset = offset;
return seg;
}
GtkTextLineSegment*
_gtk_text_line_char_to_any_segment (GtkTextLine *line, int char_offset, int *seg_offset)
{
GtkTextLineSegment *seg; int offset;
g_return_val_if_fail (line != NULL, NULL);
offset = char_offset;
seg = line->segments;
while (offset > 0 && offset >= seg->char_count)
{
offset -= seg->char_count;
seg = seg->next;
g_assert (seg != NULL); /* means an invalid byte index */
}
if (seg_offset)
*seg_offset = offset;
return seg;
}
int
_gtk_text_line_byte_to_char (GtkTextLine *line, int byte_offset)
{ int char_offset;
GtkTextLineSegment *seg;
char_offset = 0;
seg = line->segments; while (byte_offset >= seg->byte_count) /* while (we need to go farther than
the next segment) */
{
byte_offset -= seg->byte_count;
char_offset += seg->char_count;
seg = seg->next;
g_assert (seg != NULL); /* our byte_index was bogus if this happens */
}
g_assert (seg != NULL);
/* Now byte_offset is the offset into the current segment, andchar_offsetisthestartofthecurrentsegment.
Optimize the case where no chars use > 1 byte */ if (seg->byte_count == seg->char_count) return char_offset + byte_offset; else
{ if (seg->type == >k_text_char_type) return char_offset + g_utf8_strlen (seg->body.chars, byte_offset); else
{
g_assert (seg->char_count == 1);
g_assert (byte_offset == 0);
return char_offset;
}
}
}
int
_gtk_text_line_char_to_byte (GtkTextLine *line, int char_offset)
{
g_warning ("FIXME not implemented");
return0;
}
/* FIXME sync with char_locate (or figure out a clean
way to merge the two functions) */
gboolean
_gtk_text_line_byte_locate (GtkTextLine *line, int byte_offset,
GtkTextLineSegment **segment,
GtkTextLineSegment **any_segment, int *seg_byte_offset, int *line_byte_offset)
{
GtkTextLineSegment *seg;
GtkTextLineSegment *after_last_indexable;
GtkTextLineSegment *last_indexable; int offset; int bytes_in_line;
/* FIXME sync with byte_locate (or figure out a clean
way to merge the two functions) */
gboolean
_gtk_text_line_char_locate (GtkTextLine *line, int char_offset,
GtkTextLineSegment **segment,
GtkTextLineSegment **any_segment, int *seg_char_offset, int *line_char_offset)
{
GtkTextLineSegment *seg;
GtkTextLineSegment *after_last_indexable;
GtkTextLineSegment *last_indexable; int offset; int chars_in_line;
void
_gtk_text_line_byte_to_char_offsets (GtkTextLine *line, int byte_offset, int *line_char_offset, int *seg_char_offset)
{
GtkTextLineSegment *seg; int offset;
void
_gtk_text_line_char_to_byte_offsets (GtkTextLine *line, int char_offset, int *line_byte_offset, int *seg_byte_offset)
{
GtkTextLineSegment *seg; int offset;
while (offset >= seg->char_count)
{
offset -= seg->char_count;
*line_byte_offset += seg->byte_count;
seg = seg->next;
g_assert (seg != NULL); /* means an invalid char offset */
}
g_assert (seg->char_count > 0); /* indexable. */
/* offset is now the number of chars into the current segment we
want to go. Count bytes into the current segment. */
if (seg->type == >k_text_char_type)
{ constchar *p;
/* if in the last fourth of the segment walk backwards */ if (seg->char_count - offset < seg->char_count / 4)
p = g_utf8_offset_to_pointer (seg->body.chars + seg->byte_count,
offset - seg->char_count); else
p = g_utf8_offset_to_pointer (seg->body.chars, offset);
/* Algorithm: find common parent of lhs/rhs. Save the child nodes *ofthecommonparentweusedtoreachthecommonparent;the *orderingofthesechildnodesinthechildlististheordering *oflhsandrhs.
*/
/* 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;
/* 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) return1; /* lhs > rhs */ else return -1;
} elseif (iter == parent_of_lower)
{ /* lower is less than higher */
/* remember that tag == NULL means "any tag" */
GtkTextLine*
_gtk_text_line_next_could_contain_tag (GtkTextLine *line,
GtkTextBTree *tree,
GtkTextTag *tag)
{
GtkTextBTreeNode *node;
GtkTextTagInfo *info;
gboolean below_tag_root;
g_return_val_if_fail (line != NULL, NULL);
if (GTK_DEBUG_CHECK (TEXT))
_gtk_text_btree_check (tree);
if (tag == NULL)
{ /* Right now we can only offer linear-search if the user wants *toknowaboutanytagtoggleatall.
*/ return _gtk_text_line_next_excluding_last (line);
}
/* Our tag summaries only have node precision, not line *precision.Thismeansthatifanylineunderanodecouldcontaina *tag,thenanyoftheotherscouldalsocontainatag. * *Inthefuturewecouldhavesomemechanismtokeeptrackofhow *manytoggleswe'vefoundunderanodesofar,sincewehavea *countoftogglesunderthenode.ButfornowI'mgoingwithKISS.
*/
/* return same-node line, if any. */ if (line->next) return line->next;
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 last line under the tag root */
/* We need to go up out of this node, and on to the next one with togglesforthetargettag.Ifwe'rebelowthetagroot,weneedto findthenextnodebelowthetagrootthathastagsummaries.If we'renotbelowthetagroot,weneedtoseeifthetagrootis afterusinthetree,andifso,returnthefirstlineunderneath
the tag root. */
if (ordering < 0)
{ /* Tag root is ahead of us, so search there. */
node = info->tag_root; goto found;
} else
{ /* Tag root is after us, so no more lines that *couldcontainthetag.
*/ return NULL;
}
g_assert_not_reached ();
}
found:
g_assert (node != NULL);
/* We have to find the first sub-node of this node that contains *thetargettag.
*/
while (node->level > 0)
{
node = node->children.node; while (node != NULL)
{ if (gtk_text_btree_node_has_tag (node, tag)) break;
node = node->next;
}
g_assert (node != NULL); /* If this fails, it likely means an incorrecttagsummaryledusona wildgoosechasedownthisbranchof
the tree. */
}
/* See next_could_contain_tag () for more extensive comments *onwhat'sgoingonhere.
*/
g_return_val_if_fail (line != NULL, NULL);
if (GTK_DEBUG_CHECK (TEXT))
_gtk_text_btree_check (tree);
if (tag == NULL)
{ /* Right now we can only offer linear-search if the user wants *toknowaboutanytagtoggleatall.
*/ 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 *tagroot,wearebelowthetagroot,andthetag *rootexists.
*/
g_assert (line->parent->parent != NULL);
/* Create reverse-order list of nodes before *line_ancestor
*/ if (line_ancestor_parent != NULL)
node = line_ancestor_parent->children.node; else
node = line_ancestor;
if (ordering < 0)
{ /* Tag root is ahead of us, so no more lines *withthistag.
*/ return NULL;
} else
{ /* Tag root is after us, so grab last tagged *lineunderneaththetagroot.
*/
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 *thetargettag.
*/
node = found_node;
while (node->level > 0)
{
GSList *child_nodes = NULL;
GSList *iter;
g_assert (node != NULL); /* If this fails, it likely means an incorrecttagsummaryledusona wildgoosechasedownthisbranchof
the tree. */
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;
}
/* Note that the tag root and above do not have summaries forthetag;onlynodesbelowthetagroothave
the summaries. */ static gboolean
gtk_text_btree_node_has_tag (GtkTextBTreeNode *node, GtkTextTag *tag)
{
Summary *summary;
summary = node->summary; while (summary != NULL)
{ if (tag == NULL ||
summary->info->tag == tag) returnTRUE;
summary = summary->next;
}
returnFALSE;
}
/* Add node and all children to the damage region. */ #if0 staticvoid
gtk_text_btree_node_invalidate_downward (GtkTextBTreeNode *node)
{
NodeData *nd;
/* Recompute the validity and size of the view data for a given *viewatthisnodefromtheimmediatechildrenofthenode
*/ static NodeData *
gtk_text_btree_node_check_valid (GtkTextBTreeNode *node,
gpointer view_id)
{
NodeData *nd = gtk_text_btree_node_ensure_data (node, view_id);
gboolean valid; int width; int height;
staticvoid
tag_changed_cb (GtkTextTagTable *table,
GtkTextTag *tag,
gboolean size_changed,
GtkTextBTree *tree)
{ if (size_changed)
{ /* We need to queue a relayout on all regions that are tagged with *thistag.
*/
GtkTextIter start;
GtkTextIter end;
if (_gtk_text_btree_get_iter_at_first_toggle (tree, &start, tag))
{ /* Must be a last toggle if there was a first one. */
_gtk_text_btree_get_iter_at_last_toggle (tree, &end, tag);
DV (g_print ("invalidating due to tag change (%s)\n", G_STRLOC));
_gtk_text_btree_invalidate_region (tree, &start, &end, FALSE);
}
} else
{ /* We only need to queue a redraw, not a relayout */
BTreeView *view;
view = tree->views;
while (view != NULL)
{ int width = 0; int height = 0;
void
_gtk_change_node_toggle_count (GtkTextBTreeNode *node,
GtkTextTagInfo *info, int delta) /* may be negative */
{
Summary *summary, *prevPtr;
GtkTextBTreeNode *node2Ptr; int rootLevel; /* Level of original tag root */
/* valid aggregate not checked the same as width/height, because on *btreerebalancewecanhaveinvalidnodeswherealllinesbelow *themareactuallyvalid,duetomovinglinesaroundbetween *nodes. * *Theguaranteeisthatifthereareinvalidlinesthenodeis *invalid-wedon’tguaranteethatifthenodeisinvalidthere *areinvalidlines.
*/
if (nd->width != width ||
nd->height != height ||
(nd->valid && !valid))
{
g_error ("Node aggregates for view %p are invalid:\n" "Are (%d,%d,%s), should be (%d,%d,%s)",
nd->view_id,
nd->width, nd->height, nd->valid ? "TRUE" : "FALSE",
width, height, valid ? "TRUE" : "FALSE");
}
}
num_children = 0;
num_lines = 0;
num_chars = 0; if (node->level == 0)
{ for (line = node->children.line; line != NULL;
line = line->next)
{ if (line->parent != node)
{
g_error ("gtk_text_btree_node_check_consistency: line doesn't point to parent");
} if (line->segments == NULL)
{
g_error ("gtk_text_btree_node_check_consistency: line has no segments");
}
ld = line->views; while (ld != NULL)
{ /* Just ensuring we don’t segv while doing this loop */
ld = ld->next;
}
for (segPtr = line->segments; segPtr != NULL; segPtr = segPtr->next)
{ if (segPtr->type->checkFunc != NULL)
{
(*segPtr->type->checkFunc)(segPtr, line);
} if ((segPtr->byte_count == 0) && (!segPtr->type->leftGravity)
&& (segPtr->next != NULL)
&& (segPtr->next->byte_count == 0)
&& (segPtr->next->type->leftGravity))
{
g_error ("gtk_text_btree_node_check_consistency: wrong segment order for gravity");
} if ((segPtr->next == NULL)
&& (segPtr->type != >k_text_char_type))
{
g_error ("gtk_text_btree_node_check_consistency: line ended with wrong type");
}
num_chars += segPtr->char_count;
}
num_children++;
num_lines++;
}
} else
{ for (childnode = node->children.node; childnode != NULL;
childnode = childnode->next)
{ if (childnode->parent != node)
{
g_error ("gtk_text_btree_node_check_consistency: GtkTextBTreeNode doesn't point to parent");
} if (childnode->level != (node->level-1))
{
g_error ("gtk_text_btree_node_check_consistency: level mismatch (%d %d)",
node->level, childnode->level);
}
gtk_text_btree_node_check_consistency (tree, childnode); for (summary = childnode->summary; summary != NULL;
summary = summary->next)
{ for (summary2 = node->summary; ;
summary2 = summary2->next)
{ if (summary2 == NULL)
{ if (summary->info->tag_root == node)
{ break;
}
g_error ("gtk_text_btree_node_check_consistency: GtkTextBTreeNode tag \"%s\" not %s",
summary->info->tag->priv->name, "present in parent summaries");
} if (summary->info == summary2->info)
{ break;
}
}
}
num_children++;
num_lines += childnode->num_lines;
num_chars += childnode->num_chars;
}
} if (num_children != node->num_children)
{
g_error ("gtk_text_btree_node_check_consistency: mismatch in num_children (%d %d)",
num_children, node->num_children);
} if (num_lines != node->num_lines)
{
g_error ("gtk_text_btree_node_check_consistency: mismatch in num_lines (%d %d)",
num_lines, node->num_lines);
} if (num_chars != node->num_chars)
{
g_error ("gtk_text_btree_node_check_consistency: mismatch in num_chars (%d %d)",
num_chars, node->num_chars);
}
for (summary = node->summary; summary != NULL;
summary = summary->next)
{ if (summary->info->toggle_count == summary->toggle_count)
{
g_error ("gtk_text_btree_node_check_consistency: found unpruned root for \"%s\"",
summary->info->tag->priv->name);
}
toggle_count = 0; if (node->level == 0)
{ for (line = node->children.line; line != NULL;
line = line->next)
{ for (segPtr = line->segments; segPtr != NULL;
segPtr = segPtr->next)
{ if ((segPtr->type != >k_text_toggle_on_type)
&& (segPtr->type != >k_text_toggle_off_type))
{ continue;
} if (segPtr->body.toggle.info == summary->info)
{ if (!segPtr->body.toggle.inNodeCounts)
g_error ("Toggle segment not in the node counts");
/* *MakesurethatthetagtogglecountsandthetagrootpointersareOK.
*/
all_tags = list_of_tags (tree->table); for (taglist = all_tags; taglist != NULL ; taglist = taglist->next)
{
tag = taglist->data;
info = gtk_text_btree_get_existing_tag_info (tree, tag); if (info != NULL)
{
node = info->tag_root; if (node == NULL)
{ if (info->toggle_count != 0)
{
g_error ("_gtk_text_btree_check found \"%s\" with toggles (%d) but no root",
tag->priv->name, info->toggle_count);
} continue; /* no ranges for the tag */
} elseif (info->toggle_count == 0)
{
g_error ("_gtk_text_btree_check found root for \"%s\" with no toggles",
tag->priv->name);
} elseif (info->toggle_count & 1)
{
g_error ("_gtk_text_btree_check found odd toggle count for \"%s\" (%d)",
tag->priv->name, info->toggle_count);
} for (summary = node->summary; summary != NULL;
summary = summary->next)
{ if (summary->info->tag == tag)
{
g_error ("_gtk_text_btree_check found root GtkTextBTreeNode with summary info");
}
}
count = 0; if (node->level > 0)
{ for (node = node->children.node ; node != NULL ;
node = node->next)
{ for (summary = node->summary; summary != NULL;
summary = summary->next)
{ if (summary->info->tag == tag)
{
count += summary->toggle_count;
}
}
}
} else
{ const GtkTextLineSegmentClass *last = NULL;
for (line = node->children.line ; line != NULL ;
line = line->next)
{ for (seg = line->segments; seg != NULL;
seg = seg->next)
{ if ((seg->type == >k_text_toggle_on_type ||
seg->type == >k_text_toggle_off_type) &&
seg->body.toggle.info->tag == tag)
{ if (last == seg->type)
g_error ("Two consecutive toggles on or off weren't merged"); if (!seg->body.toggle.inNodeCounts)
g_error ("Toggle segment not in the node counts");
last = seg->type;
count++;
}
}
}
} if (count != info->toggle_count)
{
g_error ("_gtk_text_btree_check toggle_count (%d) wrong for \"%s\" should be (%d)",
info->toggle_count, tag->priv->name, count);
}
}
}
if (node->num_lines < 2)
{
g_error ("_gtk_text_btree_check: less than 2 lines in tree");
} if (node->num_chars < 2)
{
g_error ("_gtk_text_btree_check: less than 2 chars in tree");
} while (node->level > 0)
{
node = node->children.node; while (node->next != NULL)
{
node = node->next;
}
}
line = node->children.line; while (line->next != NULL)
{
line = line->next;
}
seg = line->segments; while ((seg->type == >k_text_toggle_off_type)
|| (seg->type == >k_text_right_mark_type)
|| (seg->type == >k_text_left_mark_type))
{ /* *It’sOKtotoggleatagoffinthelastline,but *nottostartanewrange.It’salsoOKtohavemarks *inthelastline.
*/
seg = seg->next;
} if (seg->type != >k_text_char_type)
{
g_error ("_gtk_text_btree_check: last line has bogus segment type");
} if (seg->next != NULL)
{
g_error ("_gtk_text_btree_check: last line has too many segments");
} if (seg->byte_count != 1)
{
g_error ("_gtk_text_btree_check: last line has wrong # characters: %d",
seg->byte_count);
} if ((seg->body.chars[0] != '\n') || (seg->body.chars[1] != 0))
{
g_error ("_gtk_text_btree_check: last line had bad value: %s",
seg->body.chars);
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.236Bemerkung:
(vorverarbeitet am 2026-07-03)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.