diff --git a/gtk/gtkdebug.h b/gtk/gtkdebug.h index ad1035ca40..81d0d08286 100644 --- a/gtk/gtkdebug.h +++ b/gtk/gtkdebug.h @@ -36,7 +36,8 @@ typedef enum { GTK_DEBUG_MISC = 1 << 1, GTK_DEBUG_SIGNALS = 1 << 2, GTK_DEBUG_DND = 1 << 3, - GTK_DEBUG_PLUGSOCKET = 1 << 4 + GTK_DEBUG_PLUGSOCKET = 1 << 4, + GTK_DEBUG_TEXT = 1 << 5 } GtkDebugFlag; #ifdef G_ENABLE_DEBUG diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c index d80dfb24c3..4f42df6679 100644 --- a/gtk/gtkmain.c +++ b/gtk/gtkmain.c @@ -150,7 +150,8 @@ static const GDebugKey gtk_debug_keys[] = { {"misc", GTK_DEBUG_MISC}, {"signals", GTK_DEBUG_SIGNALS}, {"dnd", GTK_DEBUG_DND}, - {"plugsocket", GTK_DEBUG_PLUGSOCKET} + {"plugsocket", GTK_DEBUG_PLUGSOCKET}, + {"text", GTK_DEBUG_TEXT} }; static const guint gtk_ndebug_keys = sizeof (gtk_debug_keys) / sizeof (GDebugKey); diff --git a/gtk/gtkmarshal.list b/gtk/gtkmarshal.list index 92a110a033..3cd3c367ab 100644 --- a/gtk/gtkmarshal.list +++ b/gtk/gtkmarshal.list @@ -16,6 +16,7 @@ NONE:ENUM,FLOAT NONE:ENUM,FLOAT,BOOL NONE:INT NONE:INT,INT +NONE:INT,INT,INT NONE:INT,INT,POINTER NONE:NONE NONE:OBJECT diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list index 92a110a033..3cd3c367ab 100644 --- a/gtk/gtkmarshalers.list +++ b/gtk/gtkmarshalers.list @@ -16,6 +16,7 @@ NONE:ENUM,FLOAT NONE:ENUM,FLOAT,BOOL NONE:INT NONE:INT,INT +NONE:INT,INT,INT NONE:INT,INT,POINTER NONE:NONE NONE:OBJECT diff --git a/gtk/gtktextbtree.c b/gtk/gtktextbtree.c index 58a6790b43..ad7bb867ae 100644 --- a/gtk/gtktextbtree.c +++ b/gtk/gtktextbtree.c @@ -61,9 +61,7 @@ #include "gtktexttagtable.h" #include "gtktextlayout.h" #include "gtktextiterprivate.h" - -/* Set this from the debugger */ -gboolean gtk_text_view_debug_btree = FALSE; +#include "gtkdebug.h" /* * Types @@ -98,11 +96,12 @@ struct _NodeData { gpointer view_id; NodeData *next; - /* These node data fields mark our damage region for the btree */ - /* If -1, we must recalc width for this node */ - gint width; - /* If -1, we must recalc height for this node */ + /* Height and width of this node */ gint height; + gint width : 24; + + /* boolean indicating whether the height/width need to be recomputed */ + gint valid : 8; }; @@ -160,7 +159,6 @@ struct _BTreeView { GtkTextLayout *layout; BTreeView *next; BTreeView *prev; - GDestroyNotify line_data_destructor; }; /* @@ -234,8 +232,6 @@ static void post_insert_fixup (GtkTextBTree GtkTextLine *insert_line, gint char_count_delta, gint line_count_delta); -static void gtk_text_btree_node_invalidate_upward (GtkTextBTreeNode *node, - gpointer view_id); static void gtk_text_btree_node_adjust_toggle_count (GtkTextBTreeNode *node, GtkTextTagInfo *info, gint adjust); @@ -260,10 +256,17 @@ static void node_data_list_destroy (NodeData *nd); static NodeData *node_data_find (NodeData *nd, gpointer view_id); -static GtkTextBTreeNode *gtk_text_btree_node_new (void); -static void gtk_text_btree_node_invalidate_downward (GtkTextBTreeNode *node); -static void gtk_text_btree_node_invalidate_upward (GtkTextBTreeNode *node, - gpointer view_id); +static GtkTextBTreeNode *gtk_text_btree_node_new (void); +static void gtk_text_btree_node_invalidate_downward (GtkTextBTreeNode *node); +static void gtk_text_btree_node_invalidate_upward (GtkTextBTreeNode *node, + gpointer view_id); +static NodeData * gtk_text_btree_node_check_valid (GtkTextBTreeNode *node, + gpointer view_id); +static NodeData * gtk_text_btree_node_check_valid_downward (GtkTextBTreeNode *node, + gpointer view_id); +static void gtk_text_btree_node_check_valid_upward (GtkTextBTreeNode *node, + gpointer view_id); + static void gtk_text_btree_node_remove_view (BTreeView *view, GtkTextBTreeNode *node, gpointer view_id); @@ -273,17 +276,12 @@ static NodeData * gtk_text_btree_node_ensure_data (GtkTextBTr gpointer view_id); static void gtk_text_btree_node_remove_data (GtkTextBTreeNode *node, gpointer view_id); -static GtkTextLineData * ensure_line_data (GtkTextLine *line, - GtkTextBTree *tree, - BTreeView *view); static void gtk_text_btree_node_get_size (GtkTextBTreeNode *node, gpointer view_id, - GtkTextBTree *tree, - BTreeView *view, gint *width, - gint *height, - GtkTextLine *last_line); - + gint *height); +static GtkTextBTreeNode * gtk_text_btree_node_common_parent (GtkTextBTreeNode *node1, + GtkTextBTreeNode *node2); static void get_tree_bounds (GtkTextBTree *tree, GtkTextIter *start, GtkTextIter *end); @@ -295,7 +293,8 @@ static void tag_removed_cb (GtkTextTagTable *table, GtkTextTag *tag, GtkTextBTree *tree); static void cleanup_line (GtkTextLine *line); -static void recompute_node_counts (GtkTextBTreeNode *node); +static void recompute_node_counts (GtkTextBTree *tree, + GtkTextBTreeNode *node); static void inc_count (GtkTextTag *tag, int inc, TagInfo *tagInfoPtr); @@ -530,6 +529,7 @@ gtk_text_btree_delete (GtkTextIter *start, GtkTextBTree *tree; GtkTextLine *start_line; GtkTextLine *end_line; + GtkTextLine *deleted_lines = NULL; /* List of lines we've deleted */ gint start_byte_offset; g_return_if_fail(start != NULL); @@ -673,8 +673,8 @@ gtk_text_btree_delete (GtkTextIter *start, } curnode->num_children -= 1; - gtk_text_btree_node_invalidate_upward(curline->parent, NULL); - gtk_text_line_destroy(tree, curline); + curline->next = deleted_lines; + deleted_lines = curline; } curline = nextline; @@ -757,6 +757,9 @@ gtk_text_btree_delete (GtkTextIter *start, if (start_line != end_line) { + BTreeView *view; + GtkTextBTreeNode *ancestor_node; + GtkTextLine *prevline; for (seg = last_seg; seg != NULL; @@ -787,8 +790,63 @@ gtk_text_btree_delete (GtkTextIter *start, } prevline->next = end_line->next; } - gtk_text_btree_node_invalidate_upward(end_line->parent, NULL); - gtk_text_line_destroy(tree, end_line); + end_line->next = deleted_lines; + deleted_lines = end_line; + + /* We now fix up the per-view aggregates. We add all the height and + * width for the deleted lines to the start line, so that when revalidation + * occurs, the correct change in size is seen. + */ + ancestor_node = gtk_text_btree_node_common_parent (curnode, start_line->parent); + view = tree->views; + while (view) + { + GtkTextLine *line; + GtkTextLineData *ld; + + gint deleted_width = 0; + gint 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; + } + + if (!view->next) + gtk_text_line_destroy(tree, line); + + line = next_line; + } + + if (deleted_width > 0 || deleted_height > 0) + { + ld = gtk_text_line_get_data (start_line, view->view_id); + + /* FIXME: ld is _NOT_ necessarily non-null here, but there is currently + * no way to add ld without also validating the node, which would + * be improper at this point. + */ + g_assert (ld); + + ld->width = MAX (deleted_width, ld->width); + ld->height += deleted_height; + ld->valid = FALSE; + } + + gtk_text_btree_node_check_valid_downward (ancestor_node, view->view_id); + if (ancestor_node->parent) + gtk_text_btree_node_check_valid_upward (ancestor_node->parent, view->view_id); + + view = view->next; + } + gtk_text_btree_rebalance(tree, curnode); } @@ -809,7 +867,7 @@ gtk_text_btree_delete (GtkTextIter *start, chars_changed(tree); segments_changed(tree); - if (gtk_text_view_debug_btree) + if (gtk_debug_flags & GTK_DEBUG_TEXT) gtk_text_btree_check(tree); /* Re-initialize our iterators */ @@ -1022,6 +1080,9 @@ find_line_by_y(GtkTextBTree *tree, BTreeView *view, { gint current_y = 0; + if (gtk_debug_flags & GTK_DEBUG_TEXT) + gtk_text_btree_check(tree); + if (node->level == 0) { GtkTextLine *line; @@ -1032,18 +1093,16 @@ find_line_by_y(GtkTextBTree *tree, BTreeView *view, { GtkTextLineData *ld; - ld = ensure_line_data(line, tree, view); + ld = gtk_text_line_get_data (line, view->view_id); - g_assert(ld != NULL); - g_assert(ld->height >= 0); + if (ld) + { + if (y < (current_y + (ld ? ld->height : 0))) + return line; - if (y < (current_y + ld->height)) - { - return line; - } - - current_y += ld->height; - *line_top += ld->height; + current_y += ld->height; + *line_top += ld->height; + } line = line->next; } @@ -1061,9 +1120,7 @@ find_line_by_y(GtkTextBTree *tree, BTreeView *view, gint height; gtk_text_btree_node_get_size(child, view->view_id, - tree, view, - &width, &height, - last_line); + &width, &height); if (y < (current_y + height)) return find_line_by_y(tree, view, child, @@ -1117,16 +1174,11 @@ find_line_top_in_line_list(GtkTextBTree *tree, GtkTextLineData *ld; if (line == target_line) - { - return y; - } - - ld = ensure_line_data(line, tree, view); - - g_assert(ld != NULL); - g_assert(ld->height >= 0); - - y += ld->height; + return y; + + ld = gtk_text_line_get_data (line, view->view_id); + if (ld) + y += ld->height; line = line->next; } @@ -1192,9 +1244,7 @@ gtk_text_btree_find_line_top (GtkTextBTree *tree, else { gtk_text_btree_node_get_size(child, view->view_id, - tree, view, - &width, &height, - NULL); + &width, &height); y += height; } child = child->next; @@ -1212,10 +1262,11 @@ gtk_text_btree_find_line_top (GtkTextBTree *tree, void gtk_text_btree_add_view (GtkTextBTree *tree, - GtkTextLayout *layout, - GDestroyNotify line_data_destructor) + GtkTextLayout *layout) { BTreeView *view; + GtkTextLine *last_line; + GtkTextLineData *line_data; g_return_if_fail(tree != NULL); @@ -1223,12 +1274,26 @@ gtk_text_btree_add_view (GtkTextBTree *tree, view->view_id = layout; view->layout = layout; - view->line_data_destructor = line_data_destructor; view->next = tree->views; view->prev = NULL; tree->views = view; + + /* The last line in the buffer has identity values for the per-view + * data so that we can avoid special case checks for it in a large + * number of loops + */ + last_line = get_last_line (tree); + + line_data = g_new (GtkTextLineData, 1); + line_data->view_id = layout; + line_data->next = NULL; + line_data->width = 0; + line_data->height = 0; + line_data->valid = TRUE; + + gtk_text_line_add_data (last_line, line_data); } void @@ -1236,6 +1301,8 @@ gtk_text_btree_remove_view (GtkTextBTree *tree, gpointer view_id) { BTreeView *view; + GtkTextLine *last_line; + GtkTextLineData *line_data; g_return_if_fail(tree != NULL); @@ -1260,6 +1327,13 @@ gtk_text_btree_remove_view (GtkTextBTree *tree, if (view == tree->views) tree->views = view->next; + /* Remove the line data for the last line which we added ourselves. + * (Do this first, so that we don't try to call the view's line data destructor on it.) + */ + last_line = get_last_line (tree); + line_data = gtk_text_line_remove_data (last_line, view_id); + g_free (line_data); + gtk_text_btree_node_remove_view(view, tree->root_node, view_id); g_free(view); @@ -1291,167 +1365,8 @@ gtk_text_btree_get_view_size (GtkTextBTree *tree, g_return_if_fail(tree != NULL); g_return_if_fail(view_id != NULL); - return gtk_text_btree_node_get_size(tree->root_node, view_id, tree, NULL, - width, height, NULL); -} - -static gint -node_get_height_before_damage(GtkTextBTreeNode *node, gpointer view_id) -{ - gint height = 0; - - if (node->level == 0) - { - GtkTextLine *line; - GtkTextLineData *ld; - NodeData *nd; - - /* Don't recurse if this node is undamaged. */ - nd = node_data_find(node->node_data, view_id); - if (nd != NULL && nd->height >= 0) - return nd->height; - - line = node->children.line; - - while (line != NULL) - { - ld = gtk_text_line_get_data(line, view_id); - - if (ld != NULL && ld->height >= 0) - height += ld->height; - else - { - /* Found a damaged line. */ - return height; - } - - line = line->next; - } - - return height; - } - else - { - GtkTextBTreeNode *child; - NodeData *nd; - - /* Don't recurse if this node is undamaged. */ - nd = node_data_find(node->node_data, view_id); - if (nd != NULL && nd->height >= 0) - return nd->height; - - /* Otherwise, count height of undamaged children occuring - before the first damaged child. */ - child = node->children.node; - while (child != NULL) - { - nd = node_data_find(child->node_data, view_id); - - if (nd != NULL && nd->height >= 0) - height += nd->height; - else - { - /* found a damaged child of the node. Add any undamaged - children of the child to our height, and return. */ - return height + node_get_height_before_damage(child, view_id); - } - child = child->next; - } - - return height; - } -} - -static gint -node_get_height_after_damage(GtkTextBTreeNode *node, gpointer view_id) -{ - gint height = 0; - - if (node->level == 0) - { - GtkTextLine *line; - GtkTextLineData *ld; - NodeData *nd; - - /* Don't recurse if this node is undamaged. */ - nd = node_data_find(node->node_data, view_id); - if (nd != NULL && nd->height >= 0) - return nd->height; - - line = node->children.line; - - while (line != NULL) - { - ld = gtk_text_line_get_data(line, view_id); - - if (ld != NULL && ld->height >= 0) - height += ld->height; - else - { - /* Found a damaged line. Reset height after - damage to 0. */ - height = 0; - } - - line = line->next; - } - - return height; - } - else - { - GtkTextBTreeNode *child; - NodeData *nd; - GtkTextBTreeNode *last_damaged; - - /* Don't recurse if this node is undamaged. */ - nd = node_data_find(node->node_data, view_id); - if (nd != NULL && nd->height >= 0) - return nd->height; - - /* Otherwise, count height of undamaged children occuring - before the first damaged child. */ - last_damaged = NULL; - child = node->children.node; - while (child != NULL) - { - nd = node_data_find(child->node_data, view_id); - - if (nd != NULL && nd->height >= 0) - height += nd->height; - else - { - /* found a damaged child of the node. reset height after - damage to 0, then add undamaged stuff in this damage - node. */ - height = 0; - last_damaged = child; - } - child = child->next; - } - - /* height is now the sum of all fully undamaged child node - heights. However we need to include undamaged child nodes of - the last damaged node as well. */ - if (last_damaged != NULL) - { - height += node_get_height_after_damage(last_damaged, view_id); - } - - return height; - } -} - -void -gtk_text_btree_get_damage_range (GtkTextBTree *tree, - gpointer view_id, - gint *top_undamaged_size, - gint *bottom_undamaged_size) -{ - g_return_if_fail(tree != NULL); - - *top_undamaged_size = node_get_height_before_damage(tree->root_node, view_id); - *bottom_undamaged_size = node_get_height_after_damage(tree->root_node, view_id); + return gtk_text_btree_node_get_size(tree->root_node, view_id, + width, height); } /* @@ -1774,10 +1689,8 @@ gtk_text_btree_tag (const GtkTextIter *start_orig, segments_changed(tree); - if (gtk_text_view_debug_btree) - { - gtk_text_btree_check(tree); - } + if (gtk_debug_flags & GTK_DEBUG_TEXT) + gtk_text_btree_check(tree); } @@ -2348,27 +2261,43 @@ gtk_text_btree_char_is_invisible (const GtkTextIter *iter) */ static void -redisplay_selected_region(GtkTextBTree *tree, - GtkTextLineSegment *mark) -{ - if (mark == tree->insert_mark || - mark == tree->selection_bound_mark || - mark == NULL) +redisplay_region (GtkTextBTree *tree, + const GtkTextIter *start, + const GtkTextIter *end) +{ + BTreeView *view; + GtkTextLine *start_line, *end_line; + + if (gtk_text_iter_compare (start, end) < 0) { - /* Selection does not affect the size of the wrapped lines, so - we don't need to invalidate the lines, just repaint them. We - used to invalidate, that's why this code is like this. Needs - cleanup if you're reading this, I just wasn't sure when writing - it that I'd leave it with just the redraw. */ - BTreeView *view; - view = tree->views; - while (view != NULL) - { - gtk_text_layout_need_repaint(view->layout, 0, 0, - view->layout->width, - view->layout->height); - view = view->next; - } + const GtkTextIter *tmp = start; + start = end; + end = tmp; + } + + start_line = gtk_text_iter_get_line (start); + end_line = gtk_text_iter_get_line (end); + + view = tree->views; + while (view != NULL) + { + gint start_y, end_y; + GtkTextLineData *ld; + + start_y = gtk_text_btree_find_line_top (tree, start_line, view->view_id); + + if (end_line == start_line) + end_y = start_y; + else + end_y = gtk_text_btree_find_line_top (tree, end_line, view->view_id); + + ld = gtk_text_line_get_data (end_line, view->view_id); + if (ld) + end_y += ld->height; + + gtk_text_layout_changed (view->layout, start_y, end_y - start_y, end_y - start_y); + + view = view->next; } } @@ -2376,17 +2305,12 @@ static void redisplay_mark(GtkTextLineSegment *mark) { GtkTextIter iter; - GtkTextIter end; gtk_text_btree_get_iter_at_mark(mark->body.mark.tree, &iter, mark); - end = iter; - gtk_text_iter_forward_char(&end); - - gtk_text_btree_invalidate_region(mark->body.mark.tree, - &iter, &end); + redisplay_region (mark->body.mark.tree, &iter, &iter); } static void @@ -2440,8 +2364,15 @@ real_set_mark(GtkTextBTree *tree, if (mark != NULL) { - if (redraw_selections) - redisplay_selected_region(tree, mark); + if (redraw_selections && + (mark == tree->insert_mark || + mark == tree->selection_bound_mark)) + { + GtkTextIter old_pos; + + gtk_text_btree_get_iter_at_mark (tree, &old_pos, mark); + redisplay_region (tree, &old_pos, where); + } /* * don't let visible marks be after the final newline of the @@ -2491,9 +2422,6 @@ real_set_mark(GtkTextBTree *tree, redisplay_mark_if_visible(mark); - if (redraw_selections) - redisplay_selected_region(tree, mark); - return mark; } @@ -2509,21 +2437,35 @@ gtk_text_btree_set_mark (GtkTextBTree *tree, TRUE); } -/* real_set_mark() is a relic from when we invalidated tree portions - due to changed selection, now we just queue a draw for the - onscreen bits since the layout size hasn't changed. */ +gboolean +gtk_text_btree_get_selection_bounds (GtkTextBTree *tree, + GtkTextIter *start, + GtkTextIter *end) +{ + gtk_text_btree_get_iter_at_mark (tree, start, tree->insert_mark); + gtk_text_btree_get_iter_at_mark (tree, end, tree->selection_bound_mark); + + if (gtk_text_iter_equal(start, end)) + return FALSE; + else + { + gtk_text_iter_reorder(start, end); + return TRUE; + } +} + void gtk_text_btree_place_cursor(GtkTextBTree *tree, const GtkTextIter *iter) { - /* Redisplay what's selected now */ - /* redisplay_selected_region(tree, NULL); */ + GtkTextIter start, end; + + if (gtk_text_btree_get_selection_bounds (tree, &start, &end)) + redisplay_region(tree, &start, &end); /* Move insert AND selection_bound before we redisplay */ real_set_mark(tree, "insert", FALSE, iter, TRUE, FALSE); real_set_mark(tree, "selection_bound", FALSE, iter, TRUE, FALSE); - - redisplay_selected_region(tree, NULL); } void @@ -3125,18 +3067,14 @@ gtk_text_line_invalidate_wrap (GtkTextLine *line, { /* For now this is totally unoptimized. FIXME? - If we kept an "invalid" flag separate from the - width/height fields (i.e. didn't use -1 as the flag), - we could probably optimize the case where the width removed + We could probably optimize the case where the width removed is less than the max width for the parent node, and the case where the height is unchanged when we re-wrap. */ g_return_if_fail(ld != NULL); - - ld->width = -1; - ld->height = -1; + ld->valid = FALSE; gtk_text_btree_node_invalidate_upward(line->parent, ld->view_id); } @@ -3701,7 +3639,7 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line, g_return_val_if_fail(line != NULL, NULL); - if (gtk_text_view_debug_btree) + if (gtk_debug_flags & GTK_DEBUG_TEXT) gtk_text_btree_check (tree); if (tag == NULL) @@ -3920,12 +3858,12 @@ gtk_text_line_destroy(GtkTextBTree *tree, GtkTextLine *line) { BTreeView *view; - view = gtk_text_btree_get_view(tree, ld->view_id); + view = gtk_text_btree_get_view (tree, ld->view_id); g_assert(view != NULL); next = ld->next; - (* view->line_data_destructor) (ld); + gtk_text_layout_free_line_data (view->layout, line, ld); ld = next; } @@ -3990,8 +3928,9 @@ node_data_new(gpointer view_id) nd->view_id = view_id; nd->next = NULL; - nd->width = -1; - nd->height = -1; + nd->width = 0; + nd->height = 0; + nd->valid = FALSE; return nd; } @@ -4113,9 +4052,7 @@ gtk_text_btree_node_invalidate_downward(GtkTextBTreeNode *node) nd = node->node_data; while (nd != NULL) { - nd->width = -1; - nd->height = -1; - + nd->valid = FALSE; nd = nd->next; } @@ -4131,8 +4068,7 @@ gtk_text_btree_node_invalidate_downward(GtkTextBTreeNode *node) ld = line->views; while (ld != NULL) { - ld->width = -1; - ld->height = -1; + ld->valid = FALSE; ld = ld->next; } @@ -4166,16 +4102,12 @@ gtk_text_btree_node_invalidate_upward(GtkTextBTreeNode *node, gpointer view_id) if (view_id) { - nd = node_data_find(iter->node_data, view_id); + nd = node_data_find (iter->node_data, view_id); - if (nd != NULL) - { - if (nd->height < 0) - break; /* Once a node is -1, we know its parents are as well. */ + if (nd == NULL || !nd->valid) + break; /* Once a node is invalid, we know its parents are as well. */ - nd->width = -1; - nd->height = -1; - } + nd->valid = FALSE; } else { @@ -4184,12 +4116,11 @@ gtk_text_btree_node_invalidate_upward(GtkTextBTreeNode *node, gpointer view_id) nd = iter->node_data; while (nd != NULL) { - if (nd->width > 0 || - nd->height > 0) - should_continue = TRUE; - - nd->width = -1; - nd->height = -1; + if (nd->valid) + { + should_continue = TRUE; + nd->valid = FALSE; + } nd = nd->next; } @@ -4203,6 +4134,423 @@ gtk_text_btree_node_invalidate_upward(GtkTextBTreeNode *node, gpointer view_id) } } + +/** + * gtk_text_btree_is_valid: + * @tree: a #GtkTextBTree + * @view_id: ID for the view + * + * Check to see if the entire #GtkTextBTree is valid or not for + * the given view. + * + * Return value: %TRUE if the entire #GtkTextBTree is valid + **/ +gboolean +gtk_text_btree_is_valid (GtkTextBTree *tree, + gpointer view_id) +{ + NodeData *nd; + g_return_val_if_fail (tree != NULL, FALSE); + + nd = node_data_find (tree->root_node->node_data, view_id); + return (nd && nd->valid); +} + +typedef struct _ValidateState ValidateState; + +struct _ValidateState +{ + gint remaining_pixels; + gboolean in_validation; + gint y; + gint old_height; + gint new_height; +}; + +static void +gtk_text_btree_node_validate (BTreeView *view, + GtkTextBTreeNode *node, + gpointer view_id, + ValidateState *state) +{ + gint node_valid = TRUE; + gint node_width = 0; + gint node_height = 0; + + NodeData *nd = gtk_text_btree_node_ensure_data (node, view_id); + g_return_if_fail (!nd->valid); + + if (node->level == 0) + { + GtkTextLine *line = node->children.line; + GtkTextLineData *ld; + + /* Iterate over leading valid lines */ + while (line != NULL) + { + ld = gtk_text_line_get_data (line, view_id); + + if (!ld || !ld->valid) + break; + else if (state->in_validation) + { + state->in_validation = FALSE; + return; + } + else + { + state->y += ld->height; + node_width = MAX (ld->width, node_width); + node_height += ld->height; + } + + line = line->next; + } + + state->in_validation = TRUE; + + /* Iterate over invalid lines */ + while (line != NULL) + { + ld = gtk_text_line_get_data (line, view_id); + + if (ld && ld->valid) + break; + else + { + if (ld) + state->old_height += ld->height; + ld = gtk_text_layout_wrap (view->layout, line, ld); + state->new_height += ld->height; + + node_width = MAX (ld->width, node_width); + node_height += ld->height; + + state->remaining_pixels -= ld->height; + if (state->remaining_pixels <= 0) + { + line = line->next; + break; + } + } + + line = line->next; + } + + /* Iterate over the remaining lines */ + while (line != NULL) + { + ld = gtk_text_line_get_data (line, view_id); + state->in_validation = FALSE; + + if (!ld || !ld->valid) + node_valid = FALSE; + + if (ld) + { + node_width = MAX (ld->width, node_width); + node_height += ld->height; + } + + line = line->next; + } + } + else + { + GtkTextBTreeNode *child; + NodeData *child_nd; + + child = node->children.node; + + /* Iterate over leading valid nodes */ + while (child) + { + child_nd = gtk_text_btree_node_ensure_data (child, view_id); + + if (!child_nd->valid) + break; + else if (state->in_validation) + { + state->in_validation = FALSE; + return; + } + else + { + state->y += child_nd->height; + node_width = MAX (node_width, child_nd->width); + node_height += child_nd->height; + } + + child = child->next; + } + + /* Iterate over invalid nodes */ + while (child) + { + child_nd = gtk_text_btree_node_ensure_data (child, view_id); + + if (child_nd->valid) + break; + else + { + gtk_text_btree_node_validate (view, child, view_id, state); + + if (!child_nd->valid) + node_valid = FALSE; + node_width = MAX (node_width, child_nd->width); + node_height += child_nd->height; + + if (!state->in_validation || state->remaining_pixels <= 0) + { + child = child->next; + break; + } + } + + child = child->next; + } + + /* Iterate over the remaining lines */ + while (child) + { + child_nd = gtk_text_btree_node_ensure_data (child, view_id); + state->in_validation = FALSE; + + if (!child_nd->valid) + node_valid = FALSE; + + node_width = MAX (child_nd->width, node_width); + node_height += child_nd->height; + + child = child->next; + } + } + + nd->width = node_width; + nd->height = node_height; + nd->valid = node_valid; +} + +/** + * gtk_text_btree_validate: + * @tree: a #GtkTextBTree + * @view_id: view id + * @max_pixels: the maximum number of pixels to validate. (No more + * than one paragraph beyond this limit will be validated) + * @y: location to store starting y coordinate of validated region + * @old_height: location to store old height of validated region + * @new_height: location to store new height of validated region + * + * Validate a single contiguous invalid region of a #GtkTextBTree for + * a given view. + * + * Return value: %TRUE if a region has been validated, %FALSE if the + * entire tree was already valid. + **/ +gboolean +gtk_text_btree_validate (GtkTextBTree *tree, + gpointer view_id, + gint max_pixels, + gint *y, + gint *old_height, + gint *new_height) +{ + BTreeView *view; + + g_return_val_if_fail (tree != NULL, FALSE); + + view = gtk_text_btree_get_view (tree, view_id); + g_return_val_if_fail (view != NULL, FALSE); + + if (!gtk_text_btree_is_valid (tree, view_id)) + { + ValidateState state; + + state.remaining_pixels = max_pixels; + state.in_validation = FALSE; + state.y = 0; + state.old_height = 0; + state.new_height = 0; + + gtk_text_btree_node_validate (view, + tree->root_node, + view_id, &state); + + if (y) + *y = state.y; + if (old_height) + *old_height = state.old_height; + if (new_height) + *new_height = state.new_height; + + if (gtk_debug_flags & GTK_DEBUG_TEXT) + gtk_text_btree_check(tree); + + return TRUE; + } + else + return FALSE; +} + +static void +gtk_text_btree_node_compute_view_aggregates (GtkTextBTreeNode *node, + gpointer view_id, + gint *width_out, + gint *height_out, + gboolean *valid_out) +{ + gint width = 0; + gint height = 0; + gboolean valid = TRUE; + + if (node->level == 0) + { + GtkTextLine *line = node->children.line; + + while (line != NULL) + { + GtkTextLineData *ld = gtk_text_line_get_data (line, view_id); + + if (!ld || !ld->valid) + valid = FALSE; + + if (ld) + { + width = MAX (ld->width, width); + height += ld->height; + } + + line = line->next; + } + } + else + { + GtkTextBTreeNode *child = node->children.node; + + while (child) + { + NodeData *child_nd = node_data_find (child->node_data, view_id); + + if (!child_nd || !child_nd->valid) + valid = FALSE; + + if (child_nd) + { + width = MAX (child_nd->width, width); + height += child_nd->height; + } + + child = child->next; + } + } + + *width_out = width; + *height_out = height; + *valid_out = valid; +} + + +/* Recompute the validity and size of the view data for a given + * view at this node from the immediate children of the node + */ +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; + gint width; + gint height; + + gtk_text_btree_node_compute_view_aggregates (node, view_id, + &width, &height, &valid); + nd->width = width; + nd->height = height; + nd->valid = valid; + + return nd; +} + +static void +gtk_text_btree_node_check_valid_upward (GtkTextBTreeNode *node, + gpointer view_id) +{ + while (node) + { + gtk_text_btree_node_check_valid (node, view_id); + node = node->parent; + } +} + +static NodeData * +gtk_text_btree_node_check_valid_downward (GtkTextBTreeNode *node, + gpointer view_id) +{ + if (node->level == 0) + { + return gtk_text_btree_node_check_valid (node, view_id); + } + else + { + GtkTextBTreeNode *child = node->children.node; + + NodeData *nd = gtk_text_btree_node_ensure_data (node, view_id); + + nd->valid = TRUE; + nd->width = 0; + nd->height = 0; + + while (child) + { + NodeData *child_nd = gtk_text_btree_node_check_valid_downward (child, view_id); + + if (!child_nd->valid) + nd->valid = FALSE; + nd->width = MAX (child_nd->width, nd->width); + nd->height += child_nd->height; + + child = child->next; + } + return nd; + } +} + + + +/** + * gtk_text_btree_validate_line: + * @tree: a #GtkTextBTree + * @line: line to validate + * @view_id: view ID for the view to validate + * + * Revalidate a single line of the btree for the given view, propagate + * results up through the entire tree. + **/ +void +gtk_text_btree_validate_line (GtkTextBTree *tree, + GtkTextLine *line, + gpointer view_id) +{ + GtkTextLineData *ld; + GtkTextLine *last_line; + BTreeView *view; + + g_return_if_fail (tree != NULL); + g_return_if_fail (line != NULL); + + view = gtk_text_btree_get_view (tree, view_id); + g_return_if_fail (view != NULL); + + ld = gtk_text_line_get_data(line, view_id); + if (!ld || !ld->valid) + { + ld = gtk_text_layout_wrap (view->layout, line, ld); + last_line = get_last_line (tree); + + gtk_text_btree_node_check_valid_upward (line->parent, view_id); + } +} + static void gtk_text_btree_node_remove_view(BTreeView *view, GtkTextBTreeNode *node, gpointer view_id) { @@ -4218,10 +4566,7 @@ gtk_text_btree_node_remove_view(BTreeView *view, GtkTextBTreeNode *node, gpointe ld = gtk_text_line_remove_data(line, view_id); if (ld) - { - if (view->line_data_destructor) - (*view->line_data_destructor)(ld); - } + gtk_text_layout_free_line_data (view->layout, line, ld); line = line->next; } @@ -4295,8 +4640,7 @@ gtk_text_btree_node_ensure_data(GtkTextBTreeNode *node, gpointer view_id) nd = nd->next; } - if (nd == NULL) - { + if (nd == NULL) { nd = node_data_new(view_id); if (node->node_data) @@ -4338,111 +4682,43 @@ gtk_text_btree_node_remove_data(GtkTextBTreeNode *node, gpointer view_id) node_data_destroy(nd); } -static GtkTextLineData* -ensure_line_data(GtkTextLine *line, GtkTextBTree *tree, BTreeView *view) -{ - GtkTextLineData *ld; - - ld = gtk_text_line_get_data(line, view->view_id); - - if (ld == NULL || - ld->height < 0 || - ld->width < 0) - { - /* This function should return the passed-in line data, - OR remove the existing line data from the line, and - return a NEW line data after adding it to the line. - That is, invariant after calling the callback is that - there should be exactly one line data for this view - stored on the btree line. */ - ld = gtk_text_layout_wrap(view->layout, line, ld); - } - - return ld; -} - -/* This is the function that results in wrapping lines - and repairing the damage region of the tree. */ static void gtk_text_btree_node_get_size(GtkTextBTreeNode *node, gpointer view_id, - GtkTextBTree *tree, BTreeView *view, - gint *width, gint *height, GtkTextLine *last_line) + gint *width, gint *height) { NodeData *nd; g_return_if_fail(width != NULL); g_return_if_fail(height != NULL); - if (last_line == NULL) - { - last_line = get_last_line(tree); - } - nd = gtk_text_btree_node_ensure_data(node, view_id); - - if (nd->width >= 0 && - nd->height >= 0) + + if (width) + *width = nd->width; + if (height) + *height = nd->height; +} + +/* Find the closest common ancestor of the two nodes. FIXME: The interface + * here isn't quite right, since for a lot of operations we want to + * know which children of the common parent correspond to the two nodes + * (e.g., when computing the order of two iters) + */ +static GtkTextBTreeNode * +gtk_text_btree_node_common_parent (GtkTextBTreeNode *node1, + GtkTextBTreeNode *node2) +{ + while (node1->level < node2->level) + node1 = node1->parent; + while (node2->level < node1->level) + node2 = node2->parent; + while (node1 != node2) { - *width = nd->width; - *height = nd->height; - return; + node1 = node1->parent; + node2 = node2->parent; } - if (view == NULL) - { - view = gtk_text_btree_get_view(tree, view_id); - g_assert(view != NULL); - } - - if (node->level == 0) - { - GtkTextLine *line; - - nd->width = 0; - nd->height = 0; - - line = node->children.line; - while (line != NULL && line != last_line) - { - GtkTextLineData *ld; - - ld = ensure_line_data(line, tree, view); - - g_assert(ld != NULL); - g_assert(ld->height >= 0); - - nd->width = MAX(ld->width, nd->width); - nd->height += ld->height; - - line = line->next; - } - } - else - { - GtkTextBTreeNode *child; - - nd->width = 0; - nd->height = 0; - - child = node->children.node; - while (child != NULL) - { - gint child_width; - gint child_height; - - gtk_text_btree_node_get_size(child, view_id, tree, view, - &child_width, &child_height, - last_line); - - nd->width = MAX(child_width, nd->width); - nd->height += child_height; - - child = child->next; - } - } - - *width = nd->width; - *height = nd->height; + return node1; } /* @@ -4505,9 +4781,10 @@ tag_changed_cb(GtkTextTagTable *table, while (view != NULL) { - gtk_text_layout_need_repaint(view->layout, 0, 0, - view->layout->width, - view->layout->height); + gint width, height; + + gtk_text_btree_get_view_size (tree, view->view_id, &width, &height); + gtk_text_layout_changed (view->layout, 0, height, height); view = view->next; } @@ -4573,10 +4850,7 @@ gtk_text_btree_rebalance(GtkTextBTree *tree, new_node->summary = NULL; new_node->level = node->level + 1; new_node->children.node = node; - new_node->num_children = 1; - new_node->num_lines = node->num_lines; - new_node->num_chars = node->num_chars; - recompute_node_counts(new_node); + recompute_node_counts(tree, new_node); tree->root_node = new_node; } new_node = gtk_text_btree_node_new(); @@ -4585,7 +4859,7 @@ gtk_text_btree_rebalance(GtkTextBTree *tree, node->next = new_node; new_node->summary = NULL; new_node->level = node->level; - new_node->num_children = node->num_children - MIN_CHILDREN; + new_node->num_children = node->num_children - MIN_CHILDREN; if (node->level == 0) { for (i = MIN_CHILDREN-1, @@ -4608,12 +4882,12 @@ gtk_text_btree_rebalance(GtkTextBTree *tree, new_node->children.node = child->next; child->next = NULL; } - recompute_node_counts(node); + recompute_node_counts(tree, node); node->parent->num_children++; node = new_node; if (node->num_children <= MAX_CHILDREN) { - recompute_node_counts(node); + recompute_node_counts(tree, node); break; } } @@ -4742,7 +5016,7 @@ gtk_text_btree_rebalance(GtkTextBTree *tree, if (total_children <= MAX_CHILDREN) { - recompute_node_counts(node); + recompute_node_counts(tree, node); node->next = other->next; node->parent->num_children--; summary_list_destroy(other->summary); @@ -4766,8 +5040,8 @@ gtk_text_btree_rebalance(GtkTextBTree *tree, halfwaynode->next = NULL; } - recompute_node_counts(node); - recompute_node_counts(other); + recompute_node_counts(tree, node); + recompute_node_counts(tree, other); } node = node->parent; @@ -4802,10 +5076,8 @@ post_insert_fixup(GtkTextBTree *tree, gtk_text_btree_rebalance(tree, node); } - if (gtk_text_view_debug_btree) - { - gtk_text_btree_check(tree); - } + if (gtk_debug_flags & GTK_DEBUG_TEXT) + gtk_text_btree_check(tree); } static GtkTextTagInfo* @@ -4894,7 +5166,7 @@ gtk_text_btree_remove_tag_info(GtkTextBTree *tree, } static void -recompute_level_zero_tag_counts(GtkTextBTreeNode *node) +recompute_level_zero_counts(GtkTextBTreeNode *node) { GtkTextLine *line; GtkTextLineSegment *seg; @@ -4939,7 +5211,7 @@ recompute_level_zero_tag_counts(GtkTextBTreeNode *node) } static void -recompute_level_nonzero_tag_counts(GtkTextBTreeNode *node) +recompute_level_nonzero_counts(GtkTextBTreeNode *node) { Summary *summary; GtkTextBTreeNode *child; @@ -4996,8 +5268,9 @@ recompute_level_nonzero_tag_counts(GtkTextBTreeNode *node) */ static void -recompute_node_counts(GtkTextBTreeNode *node) +recompute_node_counts(GtkTextBTree *tree, GtkTextBTreeNode *node) { + BTreeView *view; Summary *summary, *summary2; /* @@ -5023,10 +5296,17 @@ recompute_node_counts(GtkTextBTreeNode *node) */ if (node->level == 0) - recompute_level_zero_tag_counts(node); + recompute_level_zero_counts(node); else - recompute_level_nonzero_tag_counts(node); + recompute_level_nonzero_counts(node); + view = tree->views; + while (view) + { + gtk_text_btree_node_check_valid (node, view->view_id); + view = view->next; + } + /* * Scan through the GtkTextBTreeNode's tag records again and delete any Summary * records that still have a zero count, or that have all the toggles. @@ -5352,10 +5632,8 @@ gtk_text_btree_link_segment(GtkTextLineSegment *seg, cleanup_line(line); segments_changed(tree); - if (gtk_text_view_debug_btree) - { - gtk_text_btree_check(tree); - } + if (gtk_debug_flags & GTK_DEBUG_TEXT) + gtk_text_btree_check(tree); } static void @@ -5450,6 +5728,27 @@ toggle_segment_check_func(GtkTextLineSegment *segPtr, * Debug */ +static void +gtk_text_btree_node_view_check_consistency (GtkTextBTreeNode *node, + NodeData *nd) +{ + gint width; + gint height; + gboolean valid; + + gtk_text_btree_node_compute_view_aggregates (node, nd->view_id, + &width, &height, &valid); + 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"); + } +} static void gtk_text_btree_node_check_consistency(GtkTextBTreeNode *node) @@ -5483,7 +5782,7 @@ gtk_text_btree_node_check_consistency(GtkTextBTreeNode *node) nd = node->node_data; while (nd != NULL) { - /* Make sure we don't segv doing this. */ + gtk_text_btree_node_view_check_consistency (node, nd); nd = nd->next; } @@ -5552,7 +5851,7 @@ gtk_text_btree_node_check_consistency(GtkTextBTreeNode *node) g_error("gtk_text_btree_node_check_consistency: level mismatch (%d %d)", node->level, childnode->level); } - gtk_text_btree_node_check_consistency(childnode); + gtk_text_btree_node_check_consistency (childnode); for (summary = childnode->summary; summary != NULL; summary = summary->next) { diff --git a/gtk/gtktextbtree.h b/gtk/gtktextbtree.h index 4964ec9a19..91b37af759 100644 --- a/gtk/gtktextbtree.h +++ b/gtk/gtktextbtree.h @@ -47,8 +47,7 @@ gint gtk_text_btree_find_line_top (GtkTextBTree *tree, GtkTextLine *line, gpointer view_id); void gtk_text_btree_add_view (GtkTextBTree *tree, - GtkTextLayout *layout, - GDestroyNotify line_data_destructor); + GtkTextLayout *layout); void gtk_text_btree_remove_view (GtkTextBTree *tree, gpointer view_id); void gtk_text_btree_invalidate_region (GtkTextBTree *tree, @@ -58,12 +57,17 @@ void gtk_text_btree_get_view_size (GtkTextBTree *tree, gpointer view_id, gint *width, gint *height); -void gtk_text_btree_get_damage_range (GtkTextBTree *tree, +gboolean gtk_text_btree_is_valid (GtkTextBTree *tree, + gpointer view_id); +gboolean gtk_text_btree_validate (GtkTextBTree *tree, gpointer view_id, - gint *top_undamaged_size, - gint *bottom_undamaged_size); - - + gint max_pixels, + gint *y, + gint *old_height, + gint *new_height); +void gtk_text_btree_validate_line (GtkTextBTree *tree, + GtkTextLine *line, + gpointer view_id); /* Tag */ @@ -129,29 +133,30 @@ gboolean gtk_text_btree_get_iter_at_last_toggle (GtkTextBTree *tree, /* Manipulate marks */ - GtkTextLineSegment *gtk_text_btree_set_mark (GtkTextBTree *tree, - const gchar *name, - gboolean left_gravity, - const GtkTextIter *index, - gboolean should_exist); + const gchar *name, + gboolean left_gravity, + const GtkTextIter *index, + gboolean should_exist); void gtk_text_btree_remove_mark_by_name (GtkTextBTree *tree, - const gchar *name); + const gchar *name); void gtk_text_btree_remove_mark (GtkTextBTree *tree, - GtkTextLineSegment *segment); + GtkTextLineSegment *segment); +gboolean gtk_text_btree_get_selection_bounds (GtkTextBTree *tree, + GtkTextIter *start, + GtkTextIter *end); void gtk_text_btree_place_cursor (GtkTextBTree *tree, - const GtkTextIter *where); + const GtkTextIter *where); gboolean gtk_text_btree_mark_is_insert (GtkTextBTree *tree, - GtkTextLineSegment *segment); + GtkTextLineSegment *segment); gboolean gtk_text_btree_mark_is_selection_bound (GtkTextBTree *tree, - GtkTextLineSegment *segment); + GtkTextLineSegment *segment); GtkTextLineSegment *gtk_text_btree_get_mark_by_name (GtkTextBTree *tree, - const gchar *name); + const gchar *name); GtkTextLine * gtk_text_btree_first_could_contain_tag (GtkTextBTree *tree, - GtkTextTag *tag); + GtkTextTag *tag); GtkTextLine * gtk_text_btree_last_could_contain_tag (GtkTextBTree *tree, - GtkTextTag *tag); - + GtkTextTag *tag); /* Lines */ @@ -162,8 +167,9 @@ typedef struct _GtkTextLineData GtkTextLineData; struct _GtkTextLineData { gpointer view_id; GtkTextLineData *next; - gint width; gint height; + gint width : 24; + gint valid : 8; }; /* diff --git a/gtk/gtktextbuffer.c b/gtk/gtktextbuffer.c index 55f1acba13..44621c8185 100644 --- a/gtk/gtktextbuffer.c +++ b/gtk/gtktextbuffer.c @@ -720,6 +720,20 @@ gtk_text_buffer_mark_set (GtkTextBuffer *buffer, } +/** + * gtk_text_buffer_set_mark: + * @buffer: a #GtkTextBuffer + * @mark_name: name of the mark + * @iter: location for the mark. + * @left_gravity: if the mark is created by this function, gravity for the new + * mark. + * @should_exist: if %TRUE, warn if the mark does not exist, and return + * immediately. + * + * Move the mark to the given position, if not @should_exist, create the mark. + * + * Return value: + **/ static GtkTextMark* gtk_text_buffer_set_mark(GtkTextBuffer *buffer, const gchar *mark_name, @@ -1622,16 +1636,10 @@ gtk_text_buffer_get_selection_bounds (GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end) { - gtk_text_buffer_get_iter_at_mark(buffer, start, "insert"); - gtk_text_buffer_get_iter_at_mark(buffer, end, "selection_bound"); - - if (gtk_text_iter_equal(start, end)) - return FALSE; - else - { - gtk_text_iter_reorder(start, end); - return TRUE; - } + g_return_val_if_fail (buffer != NULL, FALSE); + g_return_val_if_fail (GTK_IS_TEXT_VIEW_BUFFER (buffer), FALSE); + + return gtk_text_btree_get_selection_bounds (buffer->tree, start, end); } diff --git a/gtk/gtktextdisplay.c b/gtk/gtktextdisplay.c index b12a198e10..58c3d86178 100644 --- a/gtk/gtktextdisplay.c +++ b/gtk/gtktextdisplay.c @@ -510,10 +510,7 @@ gtk_text_layout_draw (GtkTextLayout *layout, if (width == 0 || height == 0) return; - line_list = gtk_text_layout_get_lines (layout, - y, - y + height + 1, /* one past what we draw */ - ¤t_y); + line_list = gtk_text_layout_get_lines (layout, y, y + height, ¤t_y); current_y -= y_offset; if (line_list == NULL) @@ -612,7 +609,7 @@ gtk_text_layout_draw (GtkTextLayout *layout, } current_y += line_display->height; - gtk_text_layout_free_line_display (layout, line, line_display); + gtk_text_layout_free_line_display (layout, line_display); tmp_list = g_slist_next (tmp_list); } diff --git a/gtk/gtktextiter.c b/gtk/gtktextiter.c index ee1301758b..ab389189dc 100644 --- a/gtk/gtktextiter.c +++ b/gtk/gtktextiter.c @@ -1,6 +1,7 @@ #include "gtktextiter.h" #include "gtktextbtree.h" #include "gtktextiterprivate.h" +#include "gtkdebug.h" #include typedef struct _GtkTextRealIter GtkTextRealIter; @@ -331,7 +332,7 @@ ensure_byte_offsets(GtkTextRealIter *iter) static void check_invariants(const GtkTextIter *iter) { - if (gtk_text_view_debug_btree) + if (gtk_debug_flags & GTK_DEBUG_TEXT) gtk_text_iter_check(iter); } #else diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c index a85d9fc568..e2eeb636eb 100644 --- a/gtk/gtktextlayout.c +++ b/gtk/gtktextlayout.c @@ -62,18 +62,22 @@ static GtkTextLineData *gtk_text_layout_real_wrap (GtkTextLayout *layout, /* may be NULL */ GtkTextLineData *line_data); -static void gtk_text_layout_real_invalidate (GtkTextLayout *layout, - const GtkTextIter *start, - const GtkTextIter *end); +static void gtk_text_layout_invalidated (GtkTextLayout *layout); -static void line_data_destructor (gpointer data); +static void gtk_text_layout_real_invalidate (GtkTextLayout *layout, + const GtkTextIter *start, + const GtkTextIter *end); +static void gtk_text_layout_real_free_line_data (GtkTextLayout *layout, + GtkTextLine *line, + GtkTextLineData *line_data); static void gtk_text_layout_invalidate_all (GtkTextLayout *layout); static PangoAttribute *gtk_text_attr_appearance_new (const GtkTextAppearance *appearance); enum { - NEED_REPAINT, + INVALIDATED, + CHANGED, LAST_SIGNAL }; @@ -131,15 +135,23 @@ gtk_text_layout_class_init (GtkTextLayoutClass *klass) parent_class = gtk_type_class (GTK_TYPE_OBJECT); - signals[NEED_REPAINT] = - gtk_signal_new ("need_repaint", + signals[INVALIDATED] = + gtk_signal_new ("invalidated", GTK_RUN_LAST, object_class->type, - GTK_SIGNAL_OFFSET (GtkTextLayoutClass, need_repaint), - gtk_marshal_NONE__INT_INT_INT_INT, + GTK_SIGNAL_OFFSET (GtkTextLayoutClass, invalidated), + gtk_marshal_NONE__NONE, GTK_TYPE_NONE, - 4, - GTK_TYPE_INT, + 0); + + signals[CHANGED] = + gtk_signal_new ("changed", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (GtkTextLayoutClass, changed), + gtk_marshal_NONE__INT_INT_INT, + GTK_TYPE_NONE, + 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_INT); @@ -151,6 +163,7 @@ gtk_text_layout_class_init (GtkTextLayoutClass *klass) klass->wrap = gtk_text_layout_real_wrap; klass->invalidate = gtk_text_layout_real_invalidate; + klass->free_line_data = gtk_text_layout_real_free_line_data; } void @@ -238,7 +251,7 @@ gtk_text_layout_set_buffer (GtkTextLayout *layout, gtk_object_sink (GTK_OBJECT (buffer)); gtk_object_ref (GTK_OBJECT (buffer)); - gtk_text_btree_add_view (buffer->tree, layout, line_data_destructor); + gtk_text_btree_add_view (buffer->tree, layout); } } @@ -329,13 +342,28 @@ gtk_text_layout_get_size (GtkTextLayout *layout, *height = layout->height; } -void -gtk_text_layout_need_repaint (GtkTextLayout *layout, - gint x, gint y, - gint width, gint height) +static void +gtk_text_layout_invalidated (GtkTextLayout *layout) { - gtk_signal_emit (GTK_OBJECT (layout), signals[NEED_REPAINT], - x, y, width, height); + gtk_signal_emit (GTK_OBJECT (layout), signals[INVALIDATED]); +} + +void +gtk_text_layout_changed (GtkTextLayout *layout, + gint y, + gint old_height, + gint new_height) +{ + gtk_signal_emit (GTK_OBJECT (layout), signals[CHANGED], y, old_height, new_height); +} + +void +gtk_text_layout_free_line_data (GtkTextLayout *layout, + GtkTextLine *line, + GtkTextLineData *line_data) +{ + (* GTK_TEXT_LAYOUT_CLASS (GTK_OBJECT (layout)->klass)->free_line_data) + (layout, line, line_data); } void @@ -383,11 +411,17 @@ gtk_text_layout_get_lines (GtkTextLayout *layout, /* -1 since bottom_y is one past */ last_btree_line = gtk_text_btree_find_line_by_y (layout->buffer->tree, layout, bottom_y - 1, NULL); - + if (!last_btree_line) last_btree_line = gtk_text_btree_get_line (layout->buffer->tree, gtk_text_btree_line_count (layout->buffer->tree) - 1, NULL); + { + GtkTextLineData *ld = gtk_text_line_get_data (last_btree_line, layout); + if (ld->height == 0) + G_BREAKPOINT(); + } + g_assert (last_btree_line != NULL); line = first_btree_line; @@ -476,18 +510,25 @@ gtk_text_layout_real_invalidate (GtkTextLayout *layout, gtk_text_view_index_spew (end_index, "invalidate end"); #endif - /* If the end is at byte 0 of a line we don't actually need to nuke - last_line, but for now we just nuke it anyway. */ last_line = gtk_text_iter_get_line (end); - line = gtk_text_iter_get_line (start); while (TRUE) { GtkTextLineData *line_data = gtk_text_line_get_data (line, layout); - if (line_data) - gtk_text_line_invalidate_wrap (line, line_data); + if (line_data && + (line != last_line || !gtk_text_iter_starts_line (end))) + { + if (layout->one_display_cache && + line == layout->one_display_cache->line) + { + GtkTextLineDisplay *tmp_display = layout->one_display_cache; + layout->one_display_cache = NULL; + gtk_text_layout_free_line_display (layout, tmp_display); + } + gtk_text_line_invalidate_wrap (line, line_data); + } if (line == last_line) break; @@ -495,19 +536,175 @@ gtk_text_layout_real_invalidate (GtkTextLayout *layout, line = gtk_text_line_next (line); } - /* FIXME yeah we could probably do a bit better here - * - * -hp - * - * We could do a lot better. Not only could we queue - * only the changed areas, layout->width/height are the - * old width and height so may be too small, as well as - * too big. - * - * -owt + gtk_text_layout_invalidated (layout); +} + +static void +gtk_text_layout_real_free_line_data (GtkTextLayout *layout, + GtkTextLine *line, + GtkTextLineData *line_data) +{ + if (layout->one_display_cache && line == layout->one_display_cache->line) + { + GtkTextLineDisplay *tmp_display = layout->one_display_cache; + layout->one_display_cache = NULL; + gtk_text_layout_free_line_display (layout, tmp_display); + } + + g_free (line_data); +} + + + +/** + * gtk_text_layout_is_valid: + * @layout: a #GtkTextLayout + * + * Check if there are any invalid regions in a #GtkTextLayout's buffer + * + * Return value: #TRUE if any invalid regions were found + **/ +gboolean +gtk_text_layout_is_valid (GtkTextLayout *layout) +{ + g_return_val_if_fail (layout != NULL, FALSE); + g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), FALSE); + + return gtk_text_btree_is_valid (layout->buffer->tree, layout); +} + +/** + * gtk_text_layout_validate_yrange: + * @layout: a #GtkTextLayout + * @anchor: iter pointing into a line that will be used as the + * coordinate origin + * @y0: offset from the top of the line pointed to by @anchor at + * which to begin validation. (The offset here is in pixels + * after validation.) + * @y1: offset from the top of the line pointed to by @anchor at + * which to end validation. (The offset here is in pixels + * after validation.) + * + * Ensure that a region of a #GtkTextLayout is valid. The ::changed + * signal will be emitted if any lines are validated. + **/ +void +gtk_text_layout_validate_yrange (GtkTextLayout *layout, + GtkTextIter *anchor, + gint y0, + gint y1) +{ + GtkTextLine *line; + GtkTextLine *first_line = NULL; + GtkTextLine *last_line = NULL; + gint seen; + gint delta_height = 0; + gint first_line_y = 0; /* Quiet GCC */ + gint last_line_y = 0; /* Quiet GCC */ + + g_return_if_fail (layout != NULL); + g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout)); + + if (y0 > 0) + y0 = 0; + if (y1 < 0) + y1 = 0; + + /* Validate backwards from the anchor line to y0 */ - gtk_text_layout_need_repaint (layout, 0, 0, - layout->width, layout->height); + line = gtk_text_iter_get_line (anchor); + seen = 0; + while (line && seen < -y0) + { + GtkTextLineData *line_data = gtk_text_line_get_data (line, layout); + if (!line_data || !line_data->valid) + { + gint old_height = line_data ? line_data->height : 0; + + gtk_text_btree_validate_line (layout->buffer->tree, line, layout); + line_data = gtk_text_line_get_data (line, layout); + + delta_height += line_data->height - old_height; + + first_line = line; + first_line_y = -seen; + if (!last_line) + { + last_line = line; + last_line_y = -seen + line_data->height; + } + } + + seen += line_data->height; + line = gtk_text_line_previous (line); + } + + /* Validate forwards to y1 */ + line = gtk_text_iter_get_line (anchor); + seen = 0; + while (line && seen < y1) + { + GtkTextLineData *line_data = gtk_text_line_get_data (line, layout); + if (!line_data || !line_data->valid) + { + gint old_height = line_data ? line_data->height : 0; + + gtk_text_btree_validate_line (layout->buffer->tree, line, layout); + line_data = gtk_text_line_get_data (line, layout); + + delta_height += line_data->height - old_height; + + if (!first_line) + { + first_line = line; + first_line_y = seen; + } + last_line = line; + last_line_y = seen + line_data->height; + } + + seen += line_data->height; + line = gtk_text_line_next (line); + } + + /* If we found and validated any invalid lines, emit the changed singal + */ + if (first_line) + { + gint line_top = gtk_text_btree_find_line_top (layout->buffer->tree, first_line, layout); + + gtk_text_layout_changed (layout, + line_top, + last_line_y - first_line_y - delta_height, + last_line_y - first_line_y); + } +} + +/** + * gtk_text_layout_validate: + * @tree: a #GtkTextLayout + * @max_pixels: the maximum number of pixels to validate. (No more + * than one paragraph beyond this limit will be validated) + * + * Validate regions of a #GtkTextLayout. The ::changed signal will + * be emitted for each region validated. + **/ +void +gtk_text_layout_validate (GtkTextLayout *layout, + gint max_pixels) +{ + gint y, old_height, new_height; + + g_return_if_fail (layout != NULL); + g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout)); + + while (max_pixels > 0 && + gtk_text_btree_validate (layout->buffer->tree, layout, max_pixels, + &y, &old_height, &new_height)) + { + max_pixels -= new_height; + gtk_text_layout_changed (layout, y, old_height, new_height); + } } static GtkTextLineData* @@ -529,7 +726,8 @@ gtk_text_layout_real_wrap (GtkTextLayout *layout, display = gtk_text_layout_get_line_display (layout, line, TRUE); line_data->width = display->width; line_data->height = display->height; - gtk_text_layout_free_line_display (layout, line, display); + line_data->valid = TRUE; + gtk_text_layout_free_line_display (layout, display); return line_data; } @@ -938,8 +1136,24 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, g_return_val_if_fail (line != NULL, NULL); - display = g_new0 (GtkTextLineDisplay, 1); + if (layout->one_display_cache) + { + if (line == layout->one_display_cache->line && + (size_only || !layout->one_display_cache->size_only)) + return layout->one_display_cache; + else + { + GtkTextLineDisplay *tmp_display = layout->one_display_cache; + layout->one_display_cache = NULL; + gtk_text_layout_free_line_display (layout, tmp_display); + } + } + display = g_new0 (GtkTextLineDisplay, 1); + + display->size_only = size_only; + display->line = line; + gtk_text_btree_get_iter_at_line (layout->buffer->tree, &iter, line, 0); /* Special-case optimization for completely @@ -1102,23 +1316,27 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, g_free (text); pango_attr_list_unref (attrs); + layout->one_display_cache = display; + return display; } void gtk_text_layout_free_line_display (GtkTextLayout *layout, - GtkTextLine *line, GtkTextLineDisplay *display) { - pango_layout_unref (display->layout); - - if (display->cursors) + if (display != layout->one_display_cache) { - g_slist_foreach (display->cursors, (GFunc)g_free, NULL); - g_slist_free (display->cursors); + pango_layout_unref (display->layout); + + if (display->cursors) + { + g_slist_foreach (display->cursors, (GFunc)g_free, NULL); + g_slist_free (display->cursors); + } + + g_free (display); } - - g_free (display); } /* FIXME: This really doesn't belong in this file ... */ @@ -1126,22 +1344,64 @@ static GtkTextLineData* gtk_text_line_data_new (GtkTextLayout *layout, GtkTextLine *line) { - GtkTextLineData *list; + GtkTextLineData *line_data; - list = g_new (GtkTextLineData, 1); + line_data = g_new (GtkTextLineData, 1); - list->view_id = layout; - list->next = NULL; - list->width = -1; - list->height = -1; + line_data->view_id = layout; + line_data->next = NULL; + line_data->width = 0; + line_data->height = 0; + line_data->valid = FALSE; - return list; + return line_data; } static void -line_data_destructor (gpointer data) +get_line_at_y (GtkTextLayout *layout, + gint y, + GtkTextLine **line, + gint *line_top) { - g_free (data); + if (y < 0) + y = 0; + if (y > layout->height) + y = layout->height; + + *line = gtk_text_btree_find_line_by_y (layout->buffer->tree, layout, y, line_top); + if (*line == NULL) + { + *line = gtk_text_btree_get_line (layout->buffer->tree, + gtk_text_btree_line_count (layout->buffer->tree) - 1, NULL); + if (line_top) + *line_top = gtk_text_btree_find_line_top (layout->buffer->tree, *line, layout); + } +} + +/** + * gtk_text_layout_get_line_at_y: + * @layout: a #GtkLayout + * @target_iter: the iterator in which the result is stored + * @y: the y positition + * @line_top: location to store the y coordinate of the + * top of the line. (Can by %NULL.) + * + * Get the iter at the beginning of the line which is displayed + * at the given y. + **/ +void +gtk_text_layout_get_line_at_y (GtkTextLayout *layout, + GtkTextIter *target_iter, + gint y, + gint *line_top) +{ + GtkTextLine *line; + + g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout)); + g_return_if_fail (target_iter != NULL); + + get_line_at_y (layout, y, &line, line_top); + gtk_text_btree_get_iter_at_line (layout->buffer->tree, target_iter, line, 0); } void @@ -1162,18 +1422,11 @@ gtk_text_layout_get_iter_at_pixel (GtkTextLayout *layout, */ if (x < 0) x = 0; - if (y < 0) - y = 0; if (x > layout->width) x = layout->width; - if (y > layout->height) - y = layout->height; - - line = gtk_text_btree_find_line_by_y (layout->buffer->tree, layout, y, &line_top); - if (line == NULL) - line = gtk_text_btree_get_line (layout->buffer->tree, - gtk_text_btree_line_count (layout->buffer->tree) - 1, NULL); - + + get_line_at_y (layout, y, &line, &line_top); + display = gtk_text_layout_get_line_display (layout, line, TRUE); x -= display->x_offset; @@ -1198,7 +1451,7 @@ gtk_text_layout_get_iter_at_pixel (GtkTextLayout *layout, while (trailing--) gtk_text_iter_forward_char (target_iter); - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); } /** @@ -1258,7 +1511,30 @@ gtk_text_layout_get_cursor_locations (GtkTextLayout *layout, weak_pos->height = pango_weak_pos.height / PANGO_SCALE; } - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); +} + +/** + * gtk_text_layout_get_line_y: + * @layout: a #GtkTextLayout + * @iter: a #GtkTextIter + * + * Find the y coordinate of the top of the paragraph containing + * the given iter. + * + * Return value: the y coordinate, in pixels. + **/ +gint +gtk_text_layout_get_line_y (GtkTextLayout *layout, + const GtkTextIter *iter) +{ + GtkTextLine *line; + + g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), 0); + g_return_val_if_fail (gtk_text_iter_get_btree (iter) == layout->buffer->tree, 0); + + line = gtk_text_iter_get_line (iter); + return gtk_text_btree_find_line_top (layout->buffer->tree, line, layout); } void @@ -1310,7 +1586,7 @@ gtk_text_layout_get_iter_location (GtkTextLayout *layout, rect->height = pango_rect.height / PANGO_SCALE; } - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); } /* Find the iter for the logical beginning of the first display line whose @@ -1363,7 +1639,7 @@ find_display_line_below (GtkTextLayout *layout, } line_top += display->bottom_margin; - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); next = gtk_text_line_next (line); if (!next) @@ -1426,7 +1702,7 @@ find_display_line_above (GtkTextLayout *layout, } line_top += display->bottom_margin; - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); line = gtk_text_line_previous (line); } @@ -1521,7 +1797,7 @@ gtk_text_layout_move_iter_to_previous_line (GtkTextLayout *layout, { gint byte_offset = 0; - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); display = gtk_text_layout_get_line_display (layout, prev_line, TRUE); tmp_list = pango_layout_get_lines (display->layout); @@ -1564,7 +1840,7 @@ gtk_text_layout_move_iter_to_previous_line (GtkTextLayout *layout, } } - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); } /** @@ -1620,7 +1896,7 @@ gtk_text_layout_move_iter_to_next_line (GtkTextLayout *layout, tmp_list = tmp_list->next; } - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); line = gtk_text_line_next (line); } @@ -1704,7 +1980,7 @@ gtk_text_layout_move_iter_to_x (GtkTextLayout *layout, tmp_list = tmp_list->next; } - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); } /** @@ -1755,7 +2031,7 @@ gtk_text_layout_move_iter_visually (GtkTextLayout *layout, count++; } - gtk_text_layout_free_line_display (layout, line, display); + gtk_text_layout_free_line_display (layout, display); if (new_index < 0) { diff --git a/gtk/gtktextlayout.h b/gtk/gtktextlayout.h index 96a7a761ce..6441fa8a23 100644 --- a/gtk/gtktextlayout.h +++ b/gtk/gtktextlayout.h @@ -54,6 +54,11 @@ struct _GtkTextLayout * over long runs with the same style. */ GtkTextStyleValues *one_style_cache; + /* A cache of one line display. Getting the same line + * many times in a row is the most common case. + */ + GtkTextLineDisplay *one_display_cache; + /* Whether we are allowed to wrap right now */ gint wrap_loop_count; }; @@ -62,8 +67,16 @@ struct _GtkTextLayoutClass { GtkObjectClass parent_class; - void (* need_repaint) (GtkTextLayout *layout, - gint x, gint y, gint width, gint height); + /* Some portion of the layout was invalidated + */ + void (* invalidated) (GtkTextLayout *layout); + + /* A range of the layout changed appearance and possibly height + */ + void (* changed) (GtkTextLayout *layout, + gint y, + gint old_height, + gint new_height); GtkTextLineData *(* wrap) (GtkTextLayout *layout, @@ -71,9 +84,12 @@ struct _GtkTextLayoutClass /* may be NULL */ GtkTextLineData *line_data); - void (* invalidate) (GtkTextLayout *layout, + void (* invalidate) (GtkTextLayout *layout, const GtkTextIter *start, - const GtkTextIter *end); + const GtkTextIter *end); + void (* free_line_data) (GtkTextLayout *layout, + GtkTextLine *line, + GtkTextLineData *line_data); }; struct _GtkTextAttrAppearance @@ -106,6 +122,9 @@ struct _GtkTextLineDisplay gint right_margin; gint top_margin; gint bottom_margin; + + gboolean size_only; + GtkTextLine *line; }; extern PangoAttrType gtk_text_attr_appearance_type; @@ -149,9 +168,12 @@ GtkTextLineDisplay *gtk_text_layout_get_line_display (GtkTextLayout *layou GtkTextLine *line, gboolean size_only); void gtk_text_layout_free_line_display (GtkTextLayout *layout, - GtkTextLine *line, GtkTextLineDisplay *display); +void gtk_text_layout_get_line_at_y (GtkTextLayout *layout, + GtkTextIter *target_iter, + gint y, + gint *line_top); void gtk_text_layout_get_iter_at_pixel (GtkTextLayout *layout, GtkTextIter *iter, gint x, @@ -159,20 +181,38 @@ void gtk_text_layout_get_iter_at_pixel (GtkTextLayout *layout, void gtk_text_layout_invalidate (GtkTextLayout *layout, const GtkTextIter *start, const GtkTextIter *end); +void gtk_text_layout_free_line_data (GtkTextLayout *layout, + GtkTextLine *line, + GtkTextLineData *line_data); +gboolean gtk_text_layout_is_valid (GtkTextLayout *layout); +void gtk_text_layout_validate_yrange (GtkTextLayout *layout, + GtkTextIter *anchor_line, + gint y0, + gint y1); +void gtk_text_layout_validate (GtkTextLayout *layout, + gint max_pixels); + + /* This function should return the passed-in line data, + OR remove the existing line data from the line, and + return a NEW line data after adding it to the line. + That is, invariant after calling the callback is that + there should be exactly one line data for this view + stored on the btree line. */ GtkTextLineData *gtk_text_layout_wrap (GtkTextLayout *layout, GtkTextLine *line, /* may be NULL */ GtkTextLineData *line_data); -void gtk_text_layout_need_repaint (GtkTextLayout *layout, - gint x, +void gtk_text_layout_changed (GtkTextLayout *layout, gint y, - gint width, - gint height); + gint old_height, + gint new_height); void gtk_text_layout_get_iter_location (GtkTextLayout *layout, const GtkTextIter *iter, GdkRectangle *rect); +gint gtk_text_layout_get_line_y (GtkTextLayout *layout, + const GtkTextIter *iter); void gtk_text_layout_get_cursor_locations (GtkTextLayout *layout, GtkTextIter *iter, GdkRectangle *strong_pos, diff --git a/gtk/gtktextmark.c b/gtk/gtktextmark.c index aee93f9169..739504732b 100644 --- a/gtk/gtktextmark.c +++ b/gtk/gtktextmark.c @@ -25,6 +25,16 @@ gtk_text_mark_is_visible(GtkTextMark *mark) return seg->body.mark.visible; } +char * +gtk_text_mark_get_name (GtkTextMark *mark) +{ + GtkTextLineSegment *seg; + + seg = (GtkTextLineSegment*)mark; + + return g_strdup (seg->body.mark.name); +} + /* * Macro that determines the size of a mark segment: */ diff --git a/gtk/gtktextmark.h b/gtk/gtktextmark.h index 3fc1e1e049..0fa2f0dd52 100644 --- a/gtk/gtktextmark.h +++ b/gtk/gtktextmark.h @@ -12,7 +12,8 @@ typedef struct _GtkTextMark GtkTextMark; void gtk_text_mark_set_visible (GtkTextMark *mark, gboolean setting); -gboolean gtk_text_mark_is_visible(GtkTextMark *mark); +gboolean gtk_text_mark_is_visible (GtkTextMark *mark); +char * gtk_text_mark_get_name (GtkTextMark *mark); #ifdef __cplusplus } diff --git a/gtk/gtktextsegment.c b/gtk/gtktextsegment.c index 85afc60510..e4effa1b5c 100644 --- a/gtk/gtktextsegment.c +++ b/gtk/gtktextsegment.c @@ -59,8 +59,7 @@ #include "gtktexttagtable.h" #include "gtktextlayout.h" #include "gtktextiterprivate.h" - -extern gboolean gtk_text_view_debug_btree; +#include "gtkdebug.h" /* *-------------------------------------------------------------- @@ -200,10 +199,8 @@ char_segment_new(const gchar *text, guint len) seg->char_count = gtk_text_view_num_utf_chars(seg->body.chars, seg->byte_count); - if (gtk_text_view_debug_btree) - { - char_segment_self_check(seg); - } + if (gtk_debug_flags & GTK_DEBUG_TEXT) + char_segment_self_check(seg); return seg; } @@ -229,10 +226,8 @@ char_segment_new_from_two_strings(const gchar *text1, guint len1, as args, since it's typically used to merge two char segments */ seg->char_count = gtk_text_view_num_utf_chars(seg->body.chars, seg->byte_count); - if (gtk_text_view_debug_btree) - { - char_segment_self_check(seg); - } + if (gtk_debug_flags & GTK_DEBUG_TEXT) + char_segment_self_check(seg); return seg; } @@ -262,7 +257,7 @@ char_segment_split_func(GtkTextLineSegment *seg, int index) g_assert(index < seg->byte_count); - if (gtk_text_view_debug_btree) + if (gtk_debug_flags & GTK_DEBUG_TEXT) { char_segment_self_check(seg); } @@ -278,7 +273,7 @@ char_segment_split_func(GtkTextLineSegment *seg, int index) new1->next = new2; new2->next = seg->next; - if (gtk_text_view_debug_btree) + if (gtk_debug_flags & GTK_DEBUG_TEXT) { char_segment_self_check(new1); char_segment_self_check(new2); @@ -316,10 +311,8 @@ char_segment_cleanup_func(segPtr, line) { GtkTextLineSegment *segPtr2, *newPtr; - if (gtk_text_view_debug_btree) - { - char_segment_self_check(segPtr); - } + if (gtk_debug_flags & GTK_DEBUG_TEXT) + char_segment_self_check(segPtr); segPtr2 = segPtr->next; if ((segPtr2 == NULL) || (segPtr2->type != >k_text_char_type)) @@ -332,10 +325,8 @@ char_segment_cleanup_func(segPtr, line) newPtr->next = segPtr2->next; - if (gtk_text_view_debug_btree) - { - char_segment_self_check(newPtr); - } + if (gtk_debug_flags & GTK_DEBUG_TEXT) + char_segment_self_check(newPtr); g_free(segPtr); g_free(segPtr2); diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 197427af4d..791fae9fc1 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -153,6 +153,10 @@ static void gtk_text_view_drag_data_received (GtkWidget *widget, guint info, guint time); +static void gtk_text_view_set_scroll_adjustments (GtkLayout *layout, + GtkAdjustment *hadj, + GtkAdjustment *vadj); + static void gtk_text_view_move_insert (GtkTextView *text_view, GtkTextViewMovementStep step, gint count, @@ -169,6 +173,7 @@ static void gtk_text_view_paste_text (GtkTextView *text_view); static void gtk_text_view_toggle_overwrite (GtkTextView *text_view); +static void gtk_text_view_validate_onscreen (GtkTextView *text_view); static void gtk_text_view_scroll_calc_now (GtkTextView *text_view); static void gtk_text_view_set_values_from_style (GtkTextView *text_view, GtkTextStyleValues *values, @@ -186,9 +191,11 @@ static void gtk_text_view_start_selection_dnd (GtkTextView *text_vi static void gtk_text_view_start_cursor_blink (GtkTextView *text_view); static void gtk_text_view_stop_cursor_blink (GtkTextView *text_view); -static void gtk_text_view_commit_handler (GtkIMContext *context, - const gchar *str, - GtkTextView *text_view); +static void gtk_text_view_vadjustment_value_changed (GtkAdjustment *adj, + GtkTextView *view); +static void gtk_text_view_commit_handler (GtkIMContext *context, + const gchar *str, + GtkTextView *text_view); static void gtk_text_view_mark_set_handler (GtkTextBuffer *buffer, const GtkTextIter *location, @@ -201,12 +208,6 @@ static void gtk_text_view_set_virtual_cursor_pos (GtkTextView *text_view, gint x, gint y); -static void gtk_marshal_NONE__INT_INT_INT (GtkObject *object, - GtkSignalFunc func, - gpointer func_data, - GtkArg *args); - - enum { TARGET_STRING, TARGET_TEXT, @@ -230,7 +231,7 @@ static GtkTargetEntry target_table[] = { static guint n_targets = sizeof (target_table) / sizeof (target_table[0]); -static GtkLayout *parent_class = NULL; +static GtkLayoutClass *parent_class = NULL; static guint signals[LAST_SIGNAL] = { 0 }; GtkType @@ -286,10 +287,12 @@ gtk_text_view_class_init (GtkTextViewClass *klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; + GtkLayoutClass *layout_class; GtkBindingSet *binding_set; object_class = (GtkObjectClass*) klass; widget_class = (GtkWidgetClass*) klass; + layout_class = (GtkLayoutClass*) klass; parent_class = gtk_type_class (GTK_TYPE_LAYOUT); @@ -545,6 +548,8 @@ gtk_text_view_class_init (GtkTextViewClass *klass) widget_class->drag_motion = gtk_text_view_drag_motion; widget_class->drag_drop = gtk_text_view_drag_drop; widget_class->drag_data_received = gtk_text_view_drag_data_received; + + layout_class->set_scroll_adjustments = gtk_text_view_set_scroll_adjustments; klass->move_insert = gtk_text_view_move_insert; klass->set_anchor = gtk_text_view_set_anchor; @@ -616,7 +621,7 @@ gtk_text_view_new_with_buffer (GtkTextBuffer *buffer) void gtk_text_view_set_buffer (GtkTextView *text_view, - GtkTextBuffer *buffer) + GtkTextBuffer *buffer) { g_return_if_fail (GTK_IS_TEXT_VIEW (text_view)); g_return_if_fail (buffer == NULL || GTK_IS_TEXT_VIEW_BUFFER (buffer)); @@ -636,6 +641,8 @@ gtk_text_view_set_buffer (GtkTextView *text_view, if (buffer != NULL) { + char *mark_name; + GtkTextIter start; gtk_object_ref (GTK_OBJECT (buffer)); @@ -650,6 +657,15 @@ gtk_text_view_set_buffer (GtkTextView *text_view, "__drag_target", &start, FALSE); + /* Initialize. FIXME: Allow anonymous marks and use one here + */ + mark_name = g_strdup_printf ("__first_para_%p", text_view); + text_view->first_para_mark = gtk_text_buffer_create_mark (text_view->buffer, + mark_name, + &start, TRUE); + g_free (mark_name); + text_view->first_para_pixels = 0; + gtk_signal_connect (GTK_OBJECT (text_view->buffer), "mark_set", gtk_text_view_mark_set_handler, text_view); } @@ -872,19 +888,18 @@ gtk_text_view_get_visible_rect (GtkTextView *text_view, GdkRectangle *visible_rect) { GtkWidget *widget; - gint window_x, window_y; + GtkLayout *layout; g_return_if_fail (text_view != NULL); g_return_if_fail (GTK_IS_TEXT_VIEW (text_view)); widget = GTK_WIDGET (text_view); + layout = GTK_LAYOUT (text_view); if (visible_rect) { - gdk_window_get_position (GTK_LAYOUT (text_view)->bin_window, &window_x, &window_y); - - visible_rect->x = - window_x; - visible_rect->y = - window_y; + visible_rect->x = layout->hadjustment->value; + visible_rect->y = layout->vadjustment->value; visible_rect->width = widget->allocation.width; visible_rect->height = widget->allocation.height; } @@ -910,10 +925,10 @@ gtk_text_view_set_wrap_mode (GtkTextView *text_view, } GtkWrapMode -gtk_text_view_get_wrap_mode (GtkTextView *text_view) +gtk_text_view_get_wrap_mode (GtkTextView *text_view) { - g_return_if_fail (text_view != NULL); - g_return_if_fail (GTK_IS_TEXT_VIEW (text_view)); + g_return_val_if_fail (text_view != NULL, GTK_WRAPMODE_NONE); + g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), GTK_WRAPMODE_NONE); return text_view->wrap_mode; } @@ -1061,6 +1076,10 @@ gtk_text_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkTextView *text_view; + gchar *mark_name; + GtkTextIter first_para; + gint y; + GtkAdjustment *vadj; text_view = GTK_TEXT_VIEW (widget); @@ -1068,76 +1087,119 @@ gtk_text_view_size_allocate (GtkWidget *widget, g_assert (widget->allocation.width == allocation->width); g_assert (widget->allocation.height == allocation->height); - + + gtk_text_view_ensure_layout (text_view); + gtk_text_layout_set_screen_width (text_view->layout, + GTK_WIDGET (text_view)->allocation.width); + + gtk_text_view_validate_onscreen (text_view); gtk_text_view_scroll_calc_now (text_view); + + /* Now adjust the value of the adjustment to keep the cursor at the same place in + * the buffer + */ + mark_name = gtk_text_mark_get_name (text_view->first_para_mark); + gtk_text_buffer_get_iter_at_mark (text_view->buffer, &first_para, mark_name); + g_free (mark_name); + + y = gtk_text_layout_get_line_y (text_view->layout, &first_para) + text_view->first_para_pixels; + + vadj = GTK_LAYOUT (text_view)->vadjustment; + if (y > vadj->upper - vadj->page_size) + y = MAX (0, vadj->upper - vadj->page_size); + + gtk_adjustment_set_value (vadj, y); } static void -need_repaint_handler (GtkTextLayout *layout, - gint x, gint y, - gint width, - gint height, - gpointer data) +gtk_text_view_validate_onscreen (GtkTextView *text_view) +{ + GtkWidget *widget = GTK_WIDGET (text_view); + + if (widget->allocation.height > 0) + { + GtkTextIter first_para; + gchar *mark_name = gtk_text_mark_get_name (text_view->first_para_mark); + gtk_text_buffer_get_iter_at_mark (text_view->buffer, &first_para, mark_name); + g_free (mark_name); + + gtk_text_layout_validate_yrange (text_view->layout, + &first_para, + 0, text_view->first_para_pixels + widget->allocation.height); + } +} + +static gboolean +first_validate_callback (gpointer data) +{ + GtkTextView *text_view = data; + + gtk_text_view_validate_onscreen (text_view); + + text_view->first_validate_idle = 0; + return FALSE; +} + +static gboolean +incremental_validate_callback (gpointer data) +{ + GtkTextView *text_view = data; + + gtk_text_layout_validate (text_view->layout, 2000); + if (gtk_text_layout_is_valid (text_view->layout)) + { + text_view->incremental_validate_idle = 0; + return FALSE; + } + else + return TRUE; +} + +static void +invalidated_handler (GtkTextLayout *layout, + gpointer data) +{ + GtkTextView *text_view; + + text_view = GTK_TEXT_VIEW (data); + + if (!text_view->first_validate_idle) + text_view->first_validate_idle = g_idle_add_full (GTK_PRIORITY_RESIZE - 1, first_validate_callback, text_view, NULL); + + if (!text_view->incremental_validate_idle) + text_view->incremental_validate_idle = g_idle_add_full (GDK_PRIORITY_REDRAW + 1, incremental_validate_callback, text_view, NULL); +} + +static void +changed_handler (GtkTextLayout *layout, + gint start_y, + gint old_height, + gint new_height, + gpointer data) { GtkTextView *text_view; GdkRectangle visible_rect; + GdkRectangle redraw_rect; text_view = GTK_TEXT_VIEW (data); - /* FIXME: Right now, the area is usually far too big, and - * sometimes too small, so we just queue the whole visible - * rectangle - */ - if (GTK_WIDGET_REALIZED (text_view)) + if (old_height == new_height) { - gtk_text_view_get_visible_rect (text_view, &visible_rect); - gdk_window_invalidate_rect (GTK_LAYOUT (text_view)->bin_window, &visible_rect, FALSE); + if (GTK_WIDGET_REALIZED (text_view)) + { + gtk_text_view_get_visible_rect (text_view, &visible_rect); + + redraw_rect.x = visible_rect.x; + redraw_rect.width = visible_rect.width; + redraw_rect.y = start_y; + redraw_rect.height = MAX (old_height, new_height); + + if (gdk_rectangle_intersect (&redraw_rect, &visible_rect, &redraw_rect)) + gdk_window_invalidate_rect (GTK_LAYOUT (text_view)->bin_window, &redraw_rect, FALSE); + } } - -#if 0 - - GdkRectangle screen; - GdkRectangle area; - GdkRectangle intersect; - - text_view = GTK_TEXT_VIEW (data); - -#if 0 - gtk_widget_queue_draw (GTK_WIDGET (text_view)); -#endif - -#if 1 - screen.x = GTK_LAYOUT (text_view)->xoffset; - screen.y = GTK_LAYOUT (text_view)->yoffset; - screen.width = GTK_WIDGET (text_view)->allocation.width; - screen.height = GTK_WIDGET (text_view)->allocation.height; - - area.x = x; - area.y = y; - area.width = width; - area.height = height; - -#if 0 - printf ("repaint needed, x: %d y: %d width: %d height: %d\n", - x, y, width, height); - printf (" screen x: %d y: %d width: %d height: %d\n", - screen.x, screen.y, screen.width, screen.height); -#endif - - if (gdk_rectangle_intersect (&screen, &area, &intersect)) - { -#if 0 - printf (" intersect, x: %d y: %d width: %d height: %d\n", - intersect.x, intersect.y, intersect.width, intersect.height); -#endif - gtk_widget_queue_draw_area (GTK_WIDGET (text_view), - intersect.x - GTK_LAYOUT (text_view)->xoffset, - intersect.y - GTK_LAYOUT (text_view)->yoffset, - intersect.width, - intersect.height); - } -#endif -#endif 0 + else + gtk_widget_queue_resize (GTK_WIDGET (text_view)); } static void @@ -1177,6 +1239,18 @@ gtk_text_view_unrealize (GtkWidget *widget) text_view = GTK_TEXT_VIEW (widget); + if (text_view->first_validate_idle) + { + g_source_remove (text_view->first_validate_idle); + text_view->first_validate_idle = 0; + } + + if (text_view->incremental_validate_idle) + { + g_source_remove (text_view->incremental_validate_idle); + text_view->incremental_validate_idle = 0; + } + gtk_text_view_destroy_layout (text_view); gtk_im_context_set_client_window (text_view->im_context, NULL); @@ -1467,15 +1541,15 @@ gtk_text_view_motion_event (GtkWidget *widget, GdkEventMotion *event) static void gtk_text_view_paint (GtkWidget *widget, GdkRectangle *area) { - GtkTextView *text; + GtkTextView *text_view; - text = GTK_TEXT_VIEW (widget); + text_view = GTK_TEXT_VIEW (widget); - g_return_if_fail (text->layout != NULL); - g_return_if_fail (GTK_LAYOUT (text)->xoffset >= 0); - g_return_if_fail (GTK_LAYOUT (text)->yoffset >= 0); - - gtk_text_view_scroll_calc_now (text); + g_return_if_fail (text_view->layout != NULL); + g_return_if_fail (GTK_LAYOUT (text_view)->xoffset >= 0); + g_return_if_fail (GTK_LAYOUT (text_view)->yoffset >= 0); + + gtk_text_view_validate_onscreen (text_view); #if 0 printf ("painting %d,%d %d x %d\n", @@ -1483,7 +1557,7 @@ gtk_text_view_paint (GtkWidget *widget, GdkRectangle *area) area->width, area->height); #endif - gtk_text_layout_draw (text->layout, + gtk_text_layout_draw (text_view->layout, widget, GTK_LAYOUT (widget)->bin_window, 0, 0, @@ -2068,6 +2142,7 @@ gtk_text_view_scroll_calc_now (GtkTextView *text_view) gtk_text_layout_get_size (text_view->layout, &width, &height); +#if 0 /* If the width is less than the screen width (likely if we have wrapping turned on for the whole widget), then we want to set the scroll region to the screen @@ -2078,6 +2153,7 @@ gtk_text_view_scroll_calc_now (GtkTextView *text_view) width = MAX (text_view->layout->screen_width, width); height = height; +#endif if (GTK_LAYOUT (text_view)->width != width || GTK_LAYOUT (text_view)->height != height) @@ -2132,8 +2208,13 @@ gtk_text_view_ensure_layout (GtkTextView *text_view) text_view->layout = gtk_text_layout_new (); gtk_signal_connect (GTK_OBJECT (text_view->layout), - "need_repaint", - GTK_SIGNAL_FUNC (need_repaint_handler), + "invalidated", + GTK_SIGNAL_FUNC (invalidated_handler), + text_view); + + gtk_signal_connect (GTK_OBJECT (text_view->layout), + "changed", + GTK_SIGNAL_FUNC (changed_handler), text_view); if (text_view->buffer) @@ -2176,34 +2257,14 @@ gtk_text_view_destroy_layout (GtkTextView *text_view) gtk_text_view_end_selection_drag (text_view, NULL); gtk_signal_disconnect_by_func (GTK_OBJECT (text_view->layout), - need_repaint_handler, text_view); + invalidated_handler, text_view); + gtk_signal_disconnect_by_func (GTK_OBJECT (text_view->layout), + changed_handler, text_view); gtk_object_unref (GTK_OBJECT (text_view->layout)); text_view->layout = NULL; } } -typedef void (*GtkSignal_NONE__INT_INT_INT) (GtkObject *object, - GtkTextViewMovementStep step, - gint count, - gboolean extend_selection, - gpointer user_data); - -static void -gtk_marshal_NONE__INT_INT_INT (GtkObject *object, - GtkSignalFunc func, - gpointer func_data, - GtkArg *args) -{ - GtkSignal_NONE__INT_INT_INT rfunc; - - rfunc = (GtkSignal_NONE__INT_INT_INT) func; - (*rfunc) (object, - GTK_VALUE_ENUM (args[0]), - GTK_VALUE_INT (args[1]), - GTK_VALUE_BOOL (args[2]), - func_data); -} - /* * DND feature @@ -2506,6 +2567,44 @@ gtk_text_view_drag_data_received (GtkWidget *widget, } } +static void +gtk_text_view_set_scroll_adjustments (GtkLayout *layout, + GtkAdjustment *hadj, + GtkAdjustment *vadj) +{ + if (layout->vadjustment && layout->vadjustment != vadj) + { + gtk_signal_disconnect_by_func (GTK_OBJECT (layout->vadjustment), + GTK_SIGNAL_FUNC (gtk_text_view_vadjustment_value_changed), layout); + } + + if (vadj) + gtk_signal_connect (GTK_OBJECT (vadj), "value_changed", + GTK_SIGNAL_FUNC (gtk_text_view_vadjustment_value_changed), layout); + + (* parent_class->set_scroll_adjustments) (layout, hadj, vadj); +} + +static void +gtk_text_view_vadjustment_value_changed (GtkAdjustment *adj, + GtkTextView *text_view) +{ + GtkTextIter iter; + gchar *mark_name; + gint line_top; + + if (text_view->layout) + { + gtk_text_layout_get_line_at_y (text_view->layout, &iter, adj->value, &line_top); + + mark_name = gtk_text_mark_get_name (text_view->first_para_mark); + gtk_text_buffer_move_mark (text_view->buffer, mark_name, &iter); + g_free (mark_name); + + text_view->first_para_pixels = adj->value - line_top; + } +} + static void gtk_text_view_commit_handler (GtkIMContext *context, const gchar *str, diff --git a/gtk/gtktextview.h b/gtk/gtktextview.h index 923db59900..edf5dc23bd 100644 --- a/gtk/gtktextview.h +++ b/gtk/gtktextview.h @@ -70,12 +70,18 @@ struct _GtkTextView { * In case a), virtual_cursor_x is preserved, but not virtual_cursor_y * In case b), both virtual_cursor_x and virtual_cursor_y are preserved. */ - gint virtual_cursor_x; /* -1 means use actual cursor position */ - gint virtual_cursor_y; /* -1 means use actual cursor position */ + gint virtual_cursor_x; /* -1 means use actual cursor position */ + gint virtual_cursor_y; /* -1 means use actual cursor position */ + + GtkTextMark *first_para_mark; /* Mark at the beginning of the first onscreen paragraph */ + gint first_para_pixels; /* Offset of top of screen in the first onscreen paragraph */ GtkTextMark *dnd_mark; guint blink_timeout; + guint first_validate_idle; /* Idle to revalidate onscreen portion, runs before resize */ + guint incremental_validate_idle; /* Idle to revalidate offscreen portions, runs after redraw */ + GtkIMContext *im_context; };