[gtk/wip/chergert/textview-caching: 15/15] textlayout: introduce caching for GtkTextLineDisplay
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/chergert/textview-caching: 15/15] textlayout: introduce caching for GtkTextLineDisplay
- Date: Sun, 28 Jul 2019 17:35:20 +0000 (UTC)
commit 9926e6ebde28956ae65e5127966fd740552fbe6a
Author: Christian Hergert <chergert redhat com>
Date: Fri Jul 26 10:40:43 2019 -0700
textlayout: introduce caching for GtkTextLineDisplay
This adds a GtkTextLineDisplayCache which can be used to cache a number
of GtkTextLineDisplay (and thus, PangoLayout) while displaying a view.
It uses a GSequence to track the position of the GtkTextLineDisplay
relative to each other, a MRU to cull the least recently used display,
and and a direct hashtable to lookup display by GtkTextLine.
We only cache lines that are to be displayed (!size_only). We may want to
either create a second collection of "size_only" lines to speed that up,
or determine that it is unnecessary (which is likely the case).
gtk/gtktextlayout.c | 468 ++++++++++++-----------
gtk/gtktextlayoutprivate.h | 25 +-
gtk/gtktextlinedisplaycache.c | 716 +++++++++++++++++++++++++++++++++++
gtk/gtktextlinedisplaycacheprivate.h | 59 +++
gtk/meson.build | 1 +
5 files changed, 1051 insertions(+), 218 deletions(-)
---
diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c
index 3196db22f8..2ee27ed762 100644
--- a/gtk/gtktextlayout.c
+++ b/gtk/gtktextlayout.c
@@ -82,6 +82,7 @@
#include "gtktextbtree.h"
#include "gtktextbufferprivate.h"
#include "gtktextiterprivate.h"
+#include "gtktextlinedisplaycacheprivate.h"
#include "gtktextutil.h"
#include "gskpango.h"
#include "gtkintl.h"
@@ -101,6 +102,9 @@ struct _GtkTextLayoutPrivate
direction only influences the direction of the cursor line.
*/
GtkTextLine *cursor_line;
+
+ /* Cache for GtkTextLineDisplay to reduce overhead creating layouts */
+ GtkTextLineDisplayCache *cache;
};
static GtkTextLineData *gtk_text_layout_real_wrap (GtkTextLayout *layout,
@@ -133,19 +137,33 @@ static void gtk_text_layout_invalidate_all (GtkTextLayout *layout);
static PangoAttribute *gtk_text_attr_appearance_new (const GtkTextAppearance *appearance);
-static void gtk_text_layout_mark_set_handler (GtkTextBuffer *buffer,
- const GtkTextIter *location,
- GtkTextMark *mark,
- gpointer data);
-static void gtk_text_layout_buffer_insert_text (GtkTextBuffer *textbuffer,
- GtkTextIter *iter,
- gchar *str,
- gint len,
- gpointer data);
-static void gtk_text_layout_buffer_delete_range (GtkTextBuffer *textbuffer,
- GtkTextIter *start,
- GtkTextIter *end,
- gpointer data);
+static void gtk_text_layout_after_mark_set_handler (GtkTextBuffer *buffer,
+ const GtkTextIter *location,
+ GtkTextMark *mark,
+ gpointer data);
+static void gtk_text_layout_after_buffer_insert_text (GtkTextBuffer *textbuffer,
+ GtkTextIter *iter,
+ gchar *str,
+ gint len,
+ gpointer data);
+static void gtk_text_layout_after_buffer_delete_range (GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer data);
+static void gtk_text_layout_before_mark_set_handler (GtkTextBuffer *buffer,
+ const GtkTextIter *location,
+ GtkTextMark *mark,
+ gpointer data);
+static void gtk_text_layout_before_buffer_insert_text (GtkTextBuffer *textbuffer,
+ GtkTextIter *iter,
+ gchar *str,
+ gint len,
+ gpointer data);
+static void gtk_text_layout_before_buffer_delete_range (GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer data);
+
static void gtk_text_layout_update_cursor_line (GtkTextLayout *layout);
@@ -182,9 +200,10 @@ G_DEFINE_TYPE_WITH_PRIVATE (GtkTextLayout, gtk_text_layout, G_TYPE_OBJECT)
static void
gtk_text_layout_dispose (GObject *object)
{
- GtkTextLayout *layout;
+ GtkTextLayout *layout = GTK_TEXT_LAYOUT (object);
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
- layout = GTK_TEXT_LAYOUT (object);
+ g_clear_pointer (&priv->cache, gtk_text_line_display_cache_free);
gtk_text_layout_set_buffer (layout, NULL);
@@ -197,8 +216,6 @@ gtk_text_layout_dispose (GObject *object)
g_clear_object (&layout->ltr_context);
g_clear_object (&layout->rtl_context);
- g_clear_pointer (&layout->one_display_cache, gtk_text_line_display_unref);
-
if (layout->preedit_attrs != NULL)
{
pango_attr_list_unref (layout->preedit_attrs);
@@ -275,7 +292,10 @@ gtk_text_layout_class_init (GtkTextLayoutClass *klass)
static void
gtk_text_layout_init (GtkTextLayout *text_layout)
{
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (text_layout);
+
text_layout->cursor_visible = TRUE;
+ priv->cache = gtk_text_line_display_cache_new ();
}
GtkTextLayout*
@@ -315,14 +335,24 @@ gtk_text_layout_set_buffer (GtkTextLayout *layout,
_gtk_text_btree_remove_view (_gtk_text_buffer_get_btree (layout->buffer),
layout);
- g_signal_handlers_disconnect_by_func (layout->buffer,
- G_CALLBACK (gtk_text_layout_mark_set_handler),
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_after_mark_set_handler),
+ layout);
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_after_buffer_insert_text),
+ layout);
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_after_buffer_delete_range),
+ layout);
+
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_before_mark_set_handler),
layout);
- g_signal_handlers_disconnect_by_func (layout->buffer,
- G_CALLBACK (gtk_text_layout_buffer_insert_text),
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_before_buffer_insert_text),
layout);
- g_signal_handlers_disconnect_by_func (layout->buffer,
- G_CALLBACK (gtk_text_layout_buffer_delete_range),
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_before_buffer_delete_range),
layout);
g_object_unref (layout->buffer);
@@ -339,11 +369,18 @@ gtk_text_layout_set_buffer (GtkTextLayout *layout,
/* Bind to all signals that move the insert mark. */
g_signal_connect_after (layout->buffer, "mark-set",
- G_CALLBACK (gtk_text_layout_mark_set_handler), layout);
+ G_CALLBACK (gtk_text_layout_after_mark_set_handler), layout);
g_signal_connect_after (layout->buffer, "insert-text",
- G_CALLBACK (gtk_text_layout_buffer_insert_text), layout);
+ G_CALLBACK (gtk_text_layout_after_buffer_insert_text), layout);
g_signal_connect_after (layout->buffer, "delete-range",
- G_CALLBACK (gtk_text_layout_buffer_delete_range), layout);
+ G_CALLBACK (gtk_text_layout_after_buffer_delete_range), layout);
+
+ g_signal_connect (layout->buffer, "mark-set",
+ G_CALLBACK (gtk_text_layout_before_mark_set_handler), layout);
+ g_signal_connect (layout->buffer, "insert-text",
+ G_CALLBACK (gtk_text_layout_before_buffer_insert_text), layout);
+ g_signal_connect (layout->buffer, "delete-range",
+ G_CALLBACK (gtk_text_layout_before_buffer_delete_range), layout);
gtk_text_layout_update_cursor_line (layout);
}
@@ -627,46 +664,26 @@ gtk_text_layout_emit_changed (GtkTextLayout *layout,
g_signal_emit (layout, signals[CHANGED], 0, y, old_height, new_height);
}
-static void
-text_layout_changed (GtkTextLayout *layout,
- gint y,
- gint old_height,
- gint new_height,
- gboolean cursors_only)
-{
- /* Check if the range intersects our cached line display,
- * and invalidate the cached line if so.
- */
- if (layout->one_display_cache)
- {
- GtkTextLine *line = layout->one_display_cache->line;
- gint cache_y = _gtk_text_btree_find_line_top (_gtk_text_buffer_get_btree (layout->buffer),
- line, layout);
- gint cache_height = layout->one_display_cache->height;
-
- if (cache_y + cache_height > y && cache_y < y + old_height)
- gtk_text_layout_invalidate_cache (layout, line, cursors_only);
- }
-
- gtk_text_layout_emit_changed (layout, y, old_height, new_height);
-}
-
void
gtk_text_layout_changed (GtkTextLayout *layout,
gint y,
gint old_height,
gint new_height)
{
- text_layout_changed (layout, y, old_height, new_height, FALSE);
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+ gtk_text_line_display_cache_invalidate_y_range (priv->cache, layout, y, old_height, FALSE);
+ gtk_text_layout_emit_changed (layout, y, old_height, new_height);
}
void
gtk_text_layout_cursors_changed (GtkTextLayout *layout,
gint y,
- gint old_height,
- gint new_height)
+ gint old_height,
+ gint new_height)
{
- text_layout_changed (layout, y, old_height, new_height, TRUE);
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+ gtk_text_line_display_cache_invalidate_y_range (priv->cache, layout, y, old_height, TRUE);
+ gtk_text_layout_emit_changed (layout, y, old_height, new_height);
}
void
@@ -819,20 +836,16 @@ gtk_text_layout_invalidate_cache (GtkTextLayout *layout,
GtkTextLine *line,
gboolean cursors_only)
{
- if (layout->one_display_cache && line == layout->one_display_cache->line)
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+
+ g_assert (GTK_IS_TEXT_LAYOUT (layout));
+
+ if (priv->cache != NULL)
{
if (cursors_only)
- {
- GtkTextLineDisplay *display = layout->one_display_cache;
-
- g_clear_pointer (&display->cursors, g_array_unref);
- display->cursors_invalid = TRUE;
- display->has_block_cursor = FALSE;
- }
+ gtk_text_line_display_cache_invalidate_cursors (priv->cache, line);
else
- {
- g_clear_pointer (&layout->one_display_cache, gtk_text_line_display_unref);
- }
+ gtk_text_line_display_cache_invalidate_line (priv->cache, line);
}
}
@@ -840,7 +853,7 @@ gtk_text_layout_invalidate_cache (GtkTextLayout *layout,
*/
static void
gtk_text_layout_invalidate_cursor_line (GtkTextLayout *layout,
- gboolean cursors_only)
+ gboolean cursors_only)
{
GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
GtkTextLineData *line_data;
@@ -849,22 +862,20 @@ gtk_text_layout_invalidate_cursor_line (GtkTextLayout *layout,
return;
line_data = _gtk_text_line_get_data (priv->cursor_line, layout);
- if (line_data)
+
+ if (line_data != NULL)
{
- if (cursors_only)
- gtk_text_layout_invalidate_cache (layout, priv->cursor_line, TRUE);
- else
- {
- gtk_text_layout_invalidate_cache (layout, priv->cursor_line, FALSE);
- _gtk_text_line_invalidate_wrap (priv->cursor_line, line_data);
- }
+ gtk_text_layout_invalidate_cache (layout, priv->cursor_line, cursors_only);
+
+ if (!cursors_only)
+ _gtk_text_line_invalidate_wrap (priv->cursor_line, line_data);
gtk_text_layout_invalidated (layout);
}
}
static void
-gtk_text_layout_update_cursor_line(GtkTextLayout *layout)
+gtk_text_layout_update_cursor_line (GtkTextLayout *layout)
{
GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
GtkTextIter iter;
@@ -873,6 +884,8 @@ gtk_text_layout_update_cursor_line(GtkTextLayout *layout)
gtk_text_buffer_get_insert (layout->buffer));
priv->cursor_line = _gtk_text_iter_get_text_line (&iter);
+
+ gtk_text_line_display_cache_set_cursor_line (priv->cache, priv->cursor_line);
}
static void
@@ -924,34 +937,8 @@ gtk_text_layout_real_invalidate_cursors (GtkTextLayout *layout,
const GtkTextIter *start,
const GtkTextIter *end)
{
- /* Check if the range intersects our cached line display,
- * and invalidate the cached line if so.
- */
- if (layout->one_display_cache)
- {
- GtkTextIter line_start, line_end;
- GtkTextLine *line = layout->one_display_cache->line;
-
- gtk_text_layout_get_iter_at_line (layout, &line_start, line, 0);
-
- line_end = line_start;
- if (!gtk_text_iter_ends_line (&line_end))
- gtk_text_iter_forward_to_line_end (&line_end);
-
- if (gtk_text_iter_compare (start, end) > 0)
- {
- const GtkTextIter *tmp = start;
- start = end;
- end = tmp;
- }
-
- if (gtk_text_iter_compare (&line_start, end) <= 0 &&
- gtk_text_iter_compare (start, &line_end) <= 0)
- {
- gtk_text_layout_invalidate_cache (layout, line, TRUE);
- }
- }
-
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+ gtk_text_line_display_cache_invalidate_range (priv->cache, layout, start, end, TRUE);
gtk_text_layout_invalidated (layout);
}
@@ -2122,10 +2109,10 @@ add_preedit_attrs (GtkTextLayout *layout,
/* Iterate over the line and fill in display->cursors.
* It’s a stripped copy of gtk_text_layout_get_line_display() */
-static void
-update_text_display_cursors (GtkTextLayout *layout,
- GtkTextLine *line,
- GtkTextLineDisplay *display)
+void
+gtk_text_layout_update_display_cursors (GtkTextLayout *layout,
+ GtkTextLine *line,
+ GtkTextLineDisplay *display)
{
GtkTextLineSegment *seg;
GtkTextIter iter;
@@ -2275,9 +2262,9 @@ tags_array_toggle_tag (GPtrArray *array,
}
GtkTextLineDisplay *
-gtk_text_layout_get_line_display (GtkTextLayout *layout,
- GtkTextLine *line,
- gboolean size_only)
+gtk_text_layout_create_display (GtkTextLayout *layout,
+ GtkTextLine *line,
+ gboolean size_only)
{
GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
GtkTextLineDisplay *display;
@@ -2305,26 +2292,10 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
g_return_val_if_fail (line != NULL, NULL);
- if (layout->one_display_cache)
- {
- if (line == layout->one_display_cache->line &&
- (size_only || !layout->one_display_cache->size_only))
- {
- if (!size_only)
- update_text_display_cursors (layout, line, layout->one_display_cache);
- return gtk_text_line_display_ref (layout->one_display_cache);
- }
- else
- {
- g_clear_pointer (&layout->one_display_cache, gtk_text_line_display_unref);
- }
- }
-
- DV (g_print ("creating one line display cache (%s)\n", G_STRLOC));
-
display = g_rc_box_new0 (GtkTextLineDisplay);
- display->size_only = size_only;
+ display->mru_link.data = display;
+ display->size_only = !!size_only;
display->line = line;
display->insert_index = -1;
@@ -2335,7 +2306,7 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
if (totally_invisible_line (layout, line, &iter))
{
display->layout = pango_layout_new (layout->ltr_context);
- return display;
+ return g_steal_pointer (&display);
}
/* Find the bidi base direction */
@@ -2347,7 +2318,7 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
line->dir_strong == PANGO_DIRECTION_NEUTRAL)
{
base_dir = (layout->keyboard_direction == GTK_TEXT_DIR_LTR) ?
- PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
+ PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
}
/* Allocate space for flat text for buffer
@@ -2371,7 +2342,7 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
seg->type == >k_text_child_type)
{
style = get_style (layout, tags);
- initial_toggle_segments = FALSE;
+ initial_toggle_segments = FALSE;
/* We have to delay setting the paragraph values until we
* hit the first texture or text segment because toggles at
@@ -2401,9 +2372,9 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
*/
gint bytes = 0;
- GtkTextLineSegment *prev_seg = NULL;
+ GtkTextLineSegment *prev_seg = NULL;
- while (seg)
+ while (seg)
{
if (seg->type == >k_text_char_type)
{
@@ -2412,35 +2383,35 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
buffer_byte_offset += seg->byte_count;
bytes += seg->byte_count;
}
- else if (seg->type == >k_text_right_mark_type ||
- seg->type == >k_text_left_mark_type)
+ else if (seg->type == >k_text_right_mark_type ||
+ seg->type == >k_text_left_mark_type)
{
- /* If we have preedit string, break out of this loop - we'll almost
- * certainly have different attributes on the preedit string
- */
-
- if (layout->preedit_len > 0 &&
- _gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer),
- seg->body.mark.obj))
- break;
-
- if (seg->body.mark.visible)
- {
- cursor_byte_offsets = g_slist_prepend (cursor_byte_offsets, GINT_TO_POINTER
(layout_byte_offset));
- cursor_segs = g_slist_prepend (cursor_segs, seg);
- if (_gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer),
- seg->body.mark.obj))
- display->insert_index = layout_byte_offset;
- }
+ /* If we have preedit string, break out of this loop - we'll almost
+ * certainly have different attributes on the preedit string
+ */
+
+ if (layout->preedit_len > 0 &&
+ _gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer),
+ seg->body.mark.obj))
+ break;
+
+ if (seg->body.mark.visible)
+ {
+ cursor_byte_offsets = g_slist_prepend (cursor_byte_offsets, GINT_TO_POINTER
(layout_byte_offset));
+ cursor_segs = g_slist_prepend (cursor_segs, seg);
+ if (_gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree
(layout->buffer),
+ seg->body.mark.obj))
+ display->insert_index = layout_byte_offset;
+ }
}
- else
- break;
+ else
+ break;
- prev_seg = seg;
+ prev_seg = seg;
seg = seg->next;
}
- seg = prev_seg; /* Back up one */
+ seg = prev_seg; /* Back up one */
add_generic_attrs (layout, &style->appearance,
bytes,
attrs, layout_byte_offset - bytes,
@@ -2503,42 +2474,42 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
/* Style may have changed, drop our
current cached style */
invalidate_cached_style (layout);
- /* Add the tag only after we have seen some non-toggle non-mark segment,
- * otherwise the tag is already accounted for by _gtk_text_btree_get_tags(). */
- if (!initial_toggle_segments)
- tags = tags_array_toggle_tag (tags, seg->body.toggle.info->tag);
+ /* Add the tag only after we have seen some non-toggle non-mark segment,
+ * otherwise the tag is already accounted for by _gtk_text_btree_get_tags(). */
+ if (!initial_toggle_segments)
+ tags = tags_array_toggle_tag (tags, seg->body.toggle.info->tag);
}
/* Marks */
else if (seg->type == >k_text_right_mark_type ||
seg->type == >k_text_left_mark_type)
{
- gint cursor_offset = 0;
-
- /* At the insertion point, add the preedit string, if any */
-
- if (_gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer),
- seg->body.mark.obj))
- {
- display->insert_index = layout_byte_offset;
-
- if (layout->preedit_len > 0)
- {
- text_allocated += layout->preedit_len;
- text = g_realloc (text, text_allocated);
+ gint cursor_offset = 0;
+
+ /* At the insertion point, add the preedit string, if any */
+
+ if (_gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer),
+ seg->body.mark.obj))
+ {
+ display->insert_index = layout_byte_offset;
+
+ if (layout->preedit_len > 0)
+ {
+ text_allocated += layout->preedit_len;
+ text = g_realloc (text, text_allocated);
- style = get_style (layout, tags);
- add_preedit_attrs (layout, style, attrs, layout_byte_offset, size_only);
- release_style (layout, style);
+ style = get_style (layout, tags);
+ add_preedit_attrs (layout, style, attrs, layout_byte_offset, size_only);
+ release_style (layout, style);
- memcpy (text + layout_byte_offset, layout->preedit_string, layout->preedit_len);
- layout_byte_offset += layout->preedit_len;
+ memcpy (text + layout_byte_offset, layout->preedit_string, layout->preedit_len);
+ layout_byte_offset += layout->preedit_len;
/* DO NOT increment the buffer byte offset for preedit */
- cursor_offset = layout->preedit_cursor - layout->preedit_len;
- }
- }
-
+ cursor_offset = layout->preedit_cursor - layout->preedit_len;
+ }
+ }
+
/* Display visible marks */
@@ -2623,17 +2594,17 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
gint excess = display->total_width - text_pixel_width;
switch (pango_layout_get_alignment (display->layout))
- {
- case PANGO_ALIGN_LEFT:
+ {
+ case PANGO_ALIGN_LEFT:
default:
- break;
- case PANGO_ALIGN_CENTER:
- display->x_offset += excess / 2;
- break;
- case PANGO_ALIGN_RIGHT:
- display->x_offset += excess;
- break;
- }
+ break;
+ case PANGO_ALIGN_CENTER:
+ display->x_offset += excess / 2;
+ break;
+ case PANGO_ALIGN_RIGHT:
+ display->x_offset += excess;
+ break;
+ }
}
/* Free this if we aren't in a loop */
@@ -2645,19 +2616,31 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
if (tags != NULL)
g_ptr_array_free (tags, TRUE);
- g_assert (layout->one_display_cache == NULL);
-
- layout->one_display_cache = gtk_text_line_display_ref (display);
-
if (saw_widget)
allocate_child_widgets (layout, display);
- return display;
+ return g_steal_pointer (&display);
+}
+
+GtkTextLineDisplay *
+gtk_text_layout_get_line_display (GtkTextLayout *layout,
+ GtkTextLine *line,
+ gboolean size_only)
+{
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+
+ return gtk_text_line_display_cache_get (priv->cache, layout, line, size_only);
}
static void
gtk_text_line_display_finalize (GtkTextLineDisplay *display)
{
+ g_assert (display != NULL);
+ g_assert (display->cache_iter == NULL);
+ g_assert (display->mru_link.prev == NULL);
+ g_assert (display->mru_link.next == NULL);
+ g_assert (display->mru_link.data == display);
+
g_clear_object (&display->layout);
g_clear_pointer (&display->cursors, g_array_unref);
}
@@ -3784,26 +3767,57 @@ gtk_text_layout_spew (GtkTextLayout *layout)
#endif
}
+static void
+gtk_text_layout_before_mark_set_handler (GtkTextBuffer *buffer,
+ const GtkTextIter *location,
+ GtkTextMark *mark,
+ gpointer data)
+{
+ GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+
+ if (mark == gtk_text_buffer_get_insert (buffer))
+ gtk_text_line_display_cache_set_cursor_line (priv->cache, NULL);
+}
+
/* Catch all situations that move the insertion point.
*/
static void
-gtk_text_layout_mark_set_handler (GtkTextBuffer *buffer,
- const GtkTextIter *location,
- GtkTextMark *mark,
- gpointer data)
+gtk_text_layout_after_mark_set_handler (GtkTextBuffer *buffer,
+ const GtkTextIter *location,
+ GtkTextMark *mark,
+ gpointer data)
{
GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
if (mark == gtk_text_buffer_get_insert (buffer))
- gtk_text_layout_update_cursor_line (layout);
+ {
+ gtk_text_layout_update_cursor_line (layout);
+ gtk_text_line_display_cache_set_cursor_line (priv->cache, priv->cursor_line);
+ }
}
static void
-gtk_text_layout_buffer_insert_text (GtkTextBuffer *textbuffer,
- GtkTextIter *iter,
- gchar *str,
- gint len,
- gpointer data)
+gtk_text_layout_before_buffer_insert_text (GtkTextBuffer *textbuffer,
+ GtkTextIter *iter,
+ gchar *str,
+ gint len,
+ gpointer data)
+{
+ GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+ GtkTextLine *line = _gtk_text_iter_get_text_line (iter);
+
+ gtk_text_line_display_cache_invalidate_line (priv->cache, line);
+}
+
+static void
+gtk_text_layout_after_buffer_insert_text (GtkTextBuffer *textbuffer,
+ GtkTextIter *iter,
+ gchar *str,
+ gint len,
+ gpointer data)
{
GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
@@ -3811,10 +3825,22 @@ gtk_text_layout_buffer_insert_text (GtkTextBuffer *textbuffer,
}
static void
-gtk_text_layout_buffer_delete_range (GtkTextBuffer *textbuffer,
- GtkTextIter *start,
- GtkTextIter *end,
- gpointer data)
+gtk_text_layout_before_buffer_delete_range (GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer data)
+{
+ GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+
+ gtk_text_line_display_cache_invalidate_range (priv->cache, layout, start, end, FALSE);
+}
+
+static void
+gtk_text_layout_after_buffer_delete_range (GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer data)
{
GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
@@ -4041,6 +4067,7 @@ gtk_text_layout_snapshot (GtkTextLayout *layout,
const GdkRectangle *clip,
float cursor_alpha)
{
+ GtkTextLayoutPrivate *priv;
GskPangoRenderer *crenderer;
GtkStyleContext *context;
gint offset_y;
@@ -4055,6 +4082,8 @@ gtk_text_layout_snapshot (GtkTextLayout *layout,
g_return_if_fail (layout->buffer != NULL);
g_return_if_fail (snapshot != NULL);
+ priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+
context = gtk_widget_get_style_context (widget);
gtk_style_context_get_color (context, &color);
@@ -4155,7 +4184,24 @@ gtk_text_layout_snapshot (GtkTextLayout *layout,
gtk_text_layout_wrap_loop_end (layout);
+ /* Only update eviction source once per snapshot */
+ gtk_text_line_display_cache_delay_eviction (priv->cache);
+
g_slist_free (line_list);
gsk_pango_renderer_release (crenderer);
}
+
+gint
+gtk_text_line_display_compare (const GtkTextLineDisplay *display1,
+ const GtkTextLineDisplay *display2,
+ GtkTextLayout *layout)
+{
+ GtkTextIter iter1;
+ GtkTextIter iter2;
+
+ gtk_text_layout_get_iter_at_line (layout, &iter1, display1->line, 0);
+ gtk_text_layout_get_iter_at_line (layout, &iter2, display2->line, 0);
+
+ return gtk_text_iter_compare (&iter1, &iter2);
+}
diff --git a/gtk/gtktextlayoutprivate.h b/gtk/gtktextlayoutprivate.h
index 7905d2b542..27da15d2bd 100644
--- a/gtk/gtktextlayoutprivate.h
+++ b/gtk/gtktextlayoutprivate.h
@@ -138,11 +138,6 @@ struct _GtkTextLayout
* over long runs with the same style. */
GtkTextAttributes *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;
@@ -223,6 +218,12 @@ struct _GtkTextLineDisplay
PangoLayout *layout;
GArray *cursors; /* indexes of cursors in the PangoLayout */
+ /* GSequenceIter backpointer for use within cache */
+ GSequenceIter *cache_iter;
+
+ /* GQueue link for use in MRU to help cull cache */
+ GList mru_link;
+
GtkTextDirection direction;
gint width; /* Width of layout */
@@ -298,8 +299,12 @@ void gtk_text_layout_wrap_loop_end (GtkTextLayout *layout);
GtkTextLineDisplay* gtk_text_layout_get_line_display (GtkTextLayout *layout,
GtkTextLine *line,
gboolean size_only);
-GtkTextLineDisplay *gtk_text_line_display_ref (GtkTextLineDisplay *display);
-void gtk_text_line_display_unref (GtkTextLineDisplay *display);
+
+GtkTextLineDisplay *gtk_text_line_display_ref (GtkTextLineDisplay *display);
+void gtk_text_line_display_unref (GtkTextLineDisplay *display);
+gint gtk_text_line_display_compare (const GtkTextLineDisplay *display1,
+ const GtkTextLineDisplay *display2,
+ GtkTextLayout *layout);
void gtk_text_layout_get_line_at_y (GtkTextLayout *layout,
GtkTextIter *target_iter,
@@ -354,6 +359,12 @@ void gtk_text_layout_get_cursor_locations (GtkTextLayout *layout,
GtkTextIter *iter,
GdkRectangle *strong_pos,
GdkRectangle *weak_pos);
+GtkTextLineDisplay *gtk_text_layout_create_display (GtkTextLayout *layout,
+ GtkTextLine *line,
+ gboolean size_only);
+void gtk_text_layout_update_display_cursors (GtkTextLayout *layout,
+ GtkTextLine *line,
+ GtkTextLineDisplay *display);
gboolean _gtk_text_layout_get_block_cursor (GtkTextLayout *layout,
GdkRectangle *pos);
gboolean gtk_text_layout_clamp_iter_to_vrange (GtkTextLayout *layout,
diff --git a/gtk/gtktextlinedisplaycache.c b/gtk/gtktextlinedisplaycache.c
new file mode 100644
index 0000000000..6129d787fc
--- /dev/null
+++ b/gtk/gtktextlinedisplaycache.c
@@ -0,0 +1,716 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtktextbtree.h"
+#include "gtktextbufferprivate.h"
+#include "gtktextiterprivate.h"
+#include "gtktextlinedisplaycacheprivate.h"
+
+#define MRU_MAX_SIZE 250
+#define BLOW_CACHE_TIMEOUT_SEC 20
+#define DEBUG_LINE_DISPLAY_CACHE 0
+
+struct _GtkTextLineDisplayCache
+{
+ GSequence *sorted_by_line;
+ GHashTable *line_to_display;
+ GtkTextLine *cursor_line;
+ GQueue mru;
+ GSource *evict_source;
+
+#if DEBUG_LINE_DISPLAY_CACHE
+ guint log_source;
+ gint hits;
+ gint misses;
+ gint inval;
+ gint inval_cursors;
+ gint inval_by_line;
+ gint inval_by_range;
+ gint inval_by_y_range;
+#endif
+};
+
+#if DEBUG_LINE_DISPLAY_CACHE
+# define STAT_ADD(val,n) ((val) += n)
+# define STAT_INC(val) STAT_ADD(val,1)
+static gboolean
+dump_stats (gpointer data)
+{
+ GtkTextLineDisplayCache *cache = data;
+ g_printerr ("%p: size=%u hits=%d misses=%d inval_total=%d "
+ "inval_cursors=%d inval_by_line=%d "
+ "inval_by_range=%d inval_by_y_range=%d\n",
+ cache, g_hash_table_size (cache->line_to_display),
+ cache->hits, cache->misses,
+ cache->inval, cache->inval_cursors,
+ cache->inval_by_line, cache->inval_by_range,
+ cache->inval_by_y_range);
+ return G_SOURCE_CONTINUE;
+}
+#else
+# define STAT_ADD(val,n)
+# define STAT_INC(val)
+#endif
+
+GtkTextLineDisplayCache *
+gtk_text_line_display_cache_new (void)
+{
+ GtkTextLineDisplayCache *ret;
+
+ ret = g_slice_new0 (GtkTextLineDisplayCache);
+ ret->sorted_by_line = g_sequence_new ((GDestroyNotify)gtk_text_line_display_unref);
+ ret->line_to_display = g_hash_table_new (NULL, NULL);
+
+#if DEBUG_LINE_DISPLAY_CACHE
+ ret->log_source = g_timeout_add_seconds (1, dump_stats, ret);
+#endif
+
+ return g_steal_pointer (&ret);
+}
+
+void
+gtk_text_line_display_cache_free (GtkTextLineDisplayCache *cache)
+{
+#if DEBUG_LINE_DISPLAY_CACHE
+ g_clear_handle_id (&cache->log_source, g_source_remove);
+#endif
+
+ g_clear_pointer (&cache->evict_source, g_source_destroy);
+ g_clear_pointer (&cache->sorted_by_line, g_sequence_free);
+ g_clear_pointer (&cache->line_to_display, g_hash_table_unref);
+ g_slice_free (GtkTextLineDisplayCache, cache);
+}
+
+static gboolean
+gtk_text_line_display_cache_blow_cb (gpointer data)
+{
+ GtkTextLineDisplayCache *cache = data;
+
+ g_assert (cache != NULL);
+
+#if DEBUG_LINE_DISPLAY_CACHE
+ g_printerr ("Evicting GtkTextLineDisplayCache\n");
+#endif
+
+ cache->evict_source = NULL;
+
+ gtk_text_line_display_cache_invalidate (cache);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+gtk_text_line_display_cache_delay_eviction (GtkTextLineDisplayCache *cache)
+{
+ g_assert (cache != NULL);
+
+ if (cache->evict_source != NULL)
+ {
+ gint64 deadline;
+
+ deadline = g_get_monotonic_time () + (BLOW_CACHE_TIMEOUT_SEC * G_USEC_PER_SEC);
+ g_source_set_ready_time (cache->evict_source, deadline);
+ }
+ else
+ {
+ guint tag;
+
+ tag = g_timeout_add_seconds (BLOW_CACHE_TIMEOUT_SEC,
+ gtk_text_line_display_cache_blow_cb,
+ cache);
+ cache->evict_source = g_main_context_find_source_by_id (NULL, tag);
+ g_source_set_name (cache->evict_source, "[gtk+] gtk_text_line_display_cache_blow_cb");
+ }
+}
+
+#if DEBUG_LINE_DISPLAY_CACHE
+static void
+check_disposition (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout)
+{
+ GSequenceIter *iter;
+ gint last = G_MAXUINT;
+
+ g_assert (cache != NULL);
+ g_assert (cache->sorted_by_line != NULL);
+ g_assert (cache->line_to_display != NULL);
+
+ for (iter = g_sequence_get_begin_iter (cache->sorted_by_line);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkTextLineDisplay *display = g_sequence_get (iter);
+ GtkTextIter text_iter;
+ guint line;
+
+ gtk_text_layout_get_iter_at_line (layout, &text_iter, display->line, 0);
+ line = gtk_text_iter_get_line (&text_iter);
+
+ g_assert_cmpint (line, >, last);
+
+ last = line;
+ }
+}
+#endif
+
+static void
+gtk_text_line_display_cache_take_display (GtkTextLineDisplayCache *cache,
+ GtkTextLineDisplay *display,
+ GtkTextLayout *layout)
+{
+ g_assert (cache != NULL);
+ g_assert (display != NULL);
+ g_assert (display->line != NULL);
+ g_assert (display->cache_iter == NULL);
+ g_assert (display->mru_link.data == display);
+ g_assert (display->mru_link.prev == NULL);
+ g_assert (display->mru_link.next == NULL);
+ g_assert (g_hash_table_lookup (cache->line_to_display, display->line) == NULL);
+
+#if DEBUG_LINE_DISPLAY_CACHE
+ check_disposition (cache, layout);
+#endif
+
+ display->cache_iter =
+ g_sequence_insert_sorted (cache->sorted_by_line,
+ display,
+ (GCompareDataFunc) gtk_text_line_display_compare,
+ layout);
+ g_hash_table_insert (cache->line_to_display, display->line, display);
+ g_queue_push_head_link (&cache->mru, &display->mru_link);
+
+ /* Cull the cache if we're at capacity */
+ while (cache->mru.length > MRU_MAX_SIZE)
+ {
+ display = g_queue_peek_tail (&cache->mru);
+
+ gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
+ }
+}
+
+/*
+ * gtk_text_line_display_cache_invalidate_display:
+ * @cache: a GtkTextLineDisplayCache
+ * @display: a GtkTextLineDisplay
+ * @cursors_only: if only the cursor positions should be invalidated
+ *
+ * If @cursors_only is TRUE, then only the cursors are invalidated. Otherwise,
+ * @display is removed from the cache.
+ *
+ * Use this function when you already have access to a display as it reduces
+ * some overhead.
+ */
+void
+gtk_text_line_display_cache_invalidate_display (GtkTextLineDisplayCache *cache,
+ GtkTextLineDisplay *display,
+ gboolean cursors_only)
+{
+ g_assert (cache != NULL);
+ g_assert (display != NULL);
+ g_assert (display->line != NULL);
+
+ if (cursors_only)
+ {
+ g_clear_pointer (&display->cursors, g_array_unref);
+ display->cursors_invalid = TRUE;
+ display->has_block_cursor = FALSE;
+ }
+ else
+ {
+ GSequenceIter *iter = g_steal_pointer (&display->cache_iter);
+
+ if (cache->cursor_line == display->line)
+ cache->cursor_line = NULL;
+
+ g_hash_table_remove (cache->line_to_display, display->line);
+ g_queue_unlink (&cache->mru, &display->mru_link);
+
+ if (iter != NULL)
+ g_sequence_remove (iter);
+ }
+
+ STAT_INC (cache->inval);
+}
+
+/*
+ * gtk_text_line_display_cache_get:
+ * @cache: a #GtkTextLineDisplayCache
+ * @layout: a GtkTextLayout
+ * @line: a GtkTextLine
+ * @size_only: if only line sizing is needed
+ *
+ * Gets a GtkTextLineDisplay for @line.
+ *
+ * If no cached display exists, a new display will be created.
+ *
+ * It's possible that calling this function will cause some existing
+ * cached displays to be released and destroyed.
+ *
+ * Returns: (transfer full) (not nullable): a #GtkTextLineDisplay
+ */
+GtkTextLineDisplay *
+gtk_text_line_display_cache_get (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout,
+ GtkTextLine *line,
+ gboolean size_only)
+{
+ GtkTextLineDisplay *display;
+
+ g_assert (cache != NULL);
+ g_assert (layout != NULL);
+ g_assert (line != NULL);
+
+ display = g_hash_table_lookup (cache->line_to_display, line);
+
+ if (display != NULL)
+ {
+ if (size_only || !display->size_only)
+ {
+ STAT_INC (cache->hits);
+
+ if (!size_only && display->line == cache->cursor_line)
+ gtk_text_layout_update_display_cursors (layout, display->line, display);
+
+ /* Move to front of MRU */
+ g_queue_unlink (&cache->mru, &display->mru_link);
+ g_queue_push_head_link (&cache->mru, &display->mru_link);
+
+ return gtk_text_line_display_ref (display);
+ }
+
+ /* We need an updated display that includes more than just
+ * sizing, so we need to drop this entry and force the layout
+ * to create a new one.
+ */
+ gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
+ }
+
+ STAT_INC (cache->misses);
+
+ g_assert (!g_hash_table_lookup (cache->line_to_display, line));
+
+ display = gtk_text_layout_create_display (layout, line, size_only);
+
+ g_assert (display != NULL);
+ g_assert (display->line == line);
+
+ if (!size_only)
+ {
+ if (line == cache->cursor_line)
+ gtk_text_layout_update_display_cursors (layout, line, display);
+
+ gtk_text_line_display_cache_take_display (cache,
+ gtk_text_line_display_ref (display),
+ layout);
+ }
+
+ return g_steal_pointer (&display);
+}
+
+void
+gtk_text_line_display_cache_invalidate (GtkTextLineDisplayCache *cache)
+{
+ g_assert (cache != NULL);
+ g_assert (cache->sorted_by_line != NULL);
+ g_assert (cache->line_to_display != NULL);
+
+ STAT_ADD (cache->inval, g_hash_table_size (cache->line_to_display));
+
+ cache->cursor_line = NULL;
+
+ while (cache->mru.head != NULL)
+ {
+ GtkTextLineDisplay *display = g_queue_peek_head (&cache->mru);
+
+ gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
+ }
+
+ g_assert (g_hash_table_size (cache->line_to_display) == 0);
+ g_assert (g_sequence_get_length (cache->sorted_by_line) == 0);
+ g_assert (cache->mru.length == 0);
+}
+
+void
+gtk_text_line_display_cache_invalidate_cursors (GtkTextLineDisplayCache *cache,
+ GtkTextLine *line)
+{
+ GtkTextLineDisplay *display;
+
+ g_assert (cache != NULL);
+ g_assert (line != NULL);
+
+ STAT_INC (cache->inval_cursors);
+
+ display = g_hash_table_lookup (cache->line_to_display, line);
+
+ if (display != NULL)
+ gtk_text_line_display_cache_invalidate_display (cache, display, TRUE);
+}
+
+/*
+ * gtk_text_line_display_cache_invalidate_line:
+ * @self: a GtkTextLineDisplayCache
+ * @line: a GtkTextLine
+ *
+ * Removes a cached display for @line.
+ *
+ * Compare to gtk_text_line_display_cache_invalidate_cursors() which
+ * only invalidates the cursors for this row.
+ */
+void
+gtk_text_line_display_cache_invalidate_line (GtkTextLineDisplayCache *cache,
+ GtkTextLine *line)
+{
+ GtkTextLineDisplay *display;
+
+ g_assert (cache != NULL);
+ g_assert (line != NULL);
+
+ display = g_hash_table_lookup (cache->line_to_display, line);
+
+ if (display != NULL)
+ gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
+
+ STAT_INC (cache->inval_by_line);
+}
+
+static GSequenceIter *
+find_iter_at_text_iter (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout,
+ const GtkTextIter *iter)
+{
+ GSequenceIter *left;
+ GSequenceIter *right;
+ GSequenceIter *mid;
+ GSequenceIter *end;
+ GtkTextLine *target;
+ guint target_lineno;
+
+ g_assert (cache != NULL);
+ g_assert (iter != NULL);
+
+ if (g_sequence_is_empty (cache->sorted_by_line))
+ return NULL;
+
+ /* gtk_text_iter_get_line() will have cached value */
+ target_lineno = gtk_text_iter_get_line (iter);
+ target = _gtk_text_iter_get_text_line (iter);
+
+ /* Get some iters so we can work with pointer compare */
+ end = g_sequence_get_end_iter (cache->sorted_by_line);
+ left = g_sequence_get_begin_iter (cache->sorted_by_line);
+ right = g_sequence_iter_prev (end);
+
+ /* We already checked for empty above */
+ g_assert (!g_sequence_iter_is_end (left));
+ g_assert (!g_sequence_iter_is_end (right));
+
+ for (;;)
+ {
+ GtkTextLineDisplay *display;
+ guint lineno;
+
+ if (left == right)
+ mid = left;
+ else
+ mid = g_sequence_range_get_midpoint (left, right);
+
+ g_assert (mid != NULL);
+ g_assert (!g_sequence_iter_is_end (mid));
+
+ if (mid == end)
+ break;
+
+ display = g_sequence_get (mid);
+
+ g_assert (display != NULL);
+ g_assert (display->line != NULL);
+ g_assert (display->cache_iter != NULL);
+
+ if (target == display->line)
+ return mid;
+
+ if (right == left)
+ break;
+
+ lineno = _gtk_text_line_get_number (display->line);
+
+ if (target_lineno < lineno)
+ right = mid;
+ else if (target_lineno > lineno)
+ left = g_sequence_iter_next (mid);
+ else
+ g_assert_not_reached ();
+ }
+
+ return NULL;
+}
+
+
+/*
+ * gtk_text_line_display_cache_invalidate_range:
+ * @cache: a GtkTextLineDisplayCache
+ * @begin: the starting text iter
+ * @end: the ending text iter
+ *
+ * Removes all GtkTextLineDisplay that fall between or including
+ * @begin and @end.
+ */
+void
+gtk_text_line_display_cache_invalidate_range (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout,
+ const GtkTextIter *begin,
+ const GtkTextIter *end,
+ gboolean cursors_only)
+{
+ GSequenceIter *begin_iter;
+ GSequenceIter *end_iter;
+ GSequenceIter *iter;
+
+ g_assert (cache != NULL);
+ g_assert (layout != NULL);
+ g_assert (begin != NULL);
+ g_assert (end != NULL);
+
+ STAT_INC (cache->inval_by_range);
+
+ /* Short-circuit, is_empty() is O(1) */
+ if (g_sequence_is_empty (cache->sorted_by_line))
+ return;
+
+ /* gtk_text_iter_order() preserving const */
+ if (gtk_text_iter_compare (begin, end) > 0)
+ {
+ const GtkTextIter *tmp = begin;
+ end = begin;
+ begin = tmp;
+ }
+
+ /* Common case, begin/end on same line. Just try to find the line by
+ * line number and invalidate it alone.
+ */
+ if G_LIKELY (_gtk_text_iter_same_line (begin, end))
+ {
+ begin_iter = find_iter_at_text_iter (cache, layout, begin);
+
+ if (begin_iter != NULL)
+ {
+ GtkTextLineDisplay *display = g_sequence_get (begin_iter);
+
+ g_assert (display != NULL);
+ g_assert (display->line != NULL);
+
+ gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only);
+ }
+
+ return;
+ }
+
+ /* Find GSequenceIter containing GtkTextLineDisplay that correspond
+ * to each of the text positions.
+ */
+ begin_iter = find_iter_at_text_iter (cache, layout, begin);
+ end_iter = find_iter_at_text_iter (cache, layout, end);
+
+ /* Short-circuit if we found nothing */
+ if (begin_iter == NULL && end_iter == NULL)
+ return;
+
+ /* If nothing matches the end, we need to walk to the end of our
+ * cached displays. We know there is a non-zero number of items
+ * in the sequence at this point, so we can iter_prev() safely.
+ */
+ if (end_iter == NULL)
+ end_iter = g_sequence_iter_prev (g_sequence_get_end_iter (cache->sorted_by_line));
+
+ /* If nothing matched the begin, we need to walk starting from
+ * the first display we have cached.
+ */
+ if (begin_iter == NULL)
+ begin_iter = g_sequence_get_begin_iter (cache->sorted_by_line);
+
+ iter = begin_iter;
+
+ for (;;)
+ {
+ GtkTextLineDisplay *display = g_sequence_get (iter);
+ GSequenceIter *next = g_sequence_iter_next (iter);
+
+ gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only);
+
+ if (iter == end_iter)
+ break;
+
+ iter = next;
+ }
+}
+
+static GSequenceIter *
+find_iter_at_at_y (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout,
+ gint y)
+{
+ GtkTextBTree *btree;
+ GSequenceIter *left;
+ GSequenceIter *right;
+ GSequenceIter *mid;
+ GSequenceIter *end;
+
+ g_assert (cache != NULL);
+ g_assert (layout != NULL);
+
+ if (g_sequence_is_empty (cache->sorted_by_line))
+ return NULL;
+
+ btree = _gtk_text_buffer_get_btree (layout->buffer);
+
+ /* Get some iters so we can work with pointer compare */
+ end = g_sequence_get_end_iter (cache->sorted_by_line);
+ left = g_sequence_get_begin_iter (cache->sorted_by_line);
+ right = g_sequence_iter_prev (end);
+
+ /* We already checked for empty above */
+ g_assert (!g_sequence_iter_is_end (left));
+ g_assert (!g_sequence_iter_is_end (right));
+
+ for (;;)
+ {
+ GtkTextLineDisplay *display;
+ gint cache_y;
+ gint cache_height;
+
+ if (left == right)
+ mid = left;
+ else
+ mid = g_sequence_range_get_midpoint (left, right);
+
+ g_assert (mid != NULL);
+ g_assert (!g_sequence_iter_is_end (mid));
+
+ if (mid == end)
+ break;
+
+ display = g_sequence_get (mid);
+
+ g_assert (display != NULL);
+ g_assert (display->line != NULL);
+
+ cache_y = _gtk_text_btree_find_line_top (btree, display->line, layout);
+ cache_height = display->height;
+
+ if (y >= cache_y && y <= (cache_y + cache_height))
+ return mid;
+
+ if (left == right)
+ break;
+
+ if (y < cache_y)
+ right = mid;
+ else if (y > (cache_y + cache_height))
+ left = g_sequence_iter_next (mid);
+ else
+ g_assert_not_reached ();
+ }
+
+ return NULL;
+}
+
+/*
+ * gtk_text_line_display_cache_invalidate_y_range:
+ * @cache: a GtkTextLineDisplayCache
+ * @y: the starting Y position
+ * @old_height: the height to invalidate
+ * @cursors_only: if only cursors should be invalidated
+ *
+ * Remove all GtkTextLineDisplay that fall into the range starting
+ * from the Y position to Y+Height.
+ */
+void
+gtk_text_line_display_cache_invalidate_y_range (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout,
+ gint y,
+ gint old_height,
+ gboolean cursors_only)
+{
+ GSequenceIter *iter;
+ GtkTextBTree *btree;
+
+ g_assert (cache != NULL);
+ g_assert (layout != NULL);
+
+ STAT_INC (cache->inval_by_y_range);
+
+ btree = _gtk_text_buffer_get_btree (layout->buffer);
+ iter = find_iter_at_at_y (cache, layout, y);
+
+ if (iter == NULL)
+ return;
+
+ while (!g_sequence_iter_is_end (iter))
+ {
+ GtkTextLineDisplay *display;
+ gint cache_y;
+ gint cache_height;
+
+ display = g_sequence_get (iter);
+ iter = g_sequence_iter_next (iter);
+
+ cache_y = _gtk_text_btree_find_line_top (btree, display->line, layout);
+ cache_height = display->height;
+
+ if (cache_y + cache_height > y && cache_y < y + old_height)
+ {
+ gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only);
+
+ y += cache_height;
+ old_height -= cache_height;
+
+ if (old_height > 0)
+ continue;
+ }
+
+ break;
+ }
+}
+
+void
+gtk_text_line_display_cache_set_cursor_line (GtkTextLineDisplayCache *cache,
+ GtkTextLine *cursor_line)
+{
+ GtkTextLineDisplay *display;
+
+ g_assert (cache != NULL);
+
+ if (cursor_line == cache->cursor_line)
+ return;
+
+ display = g_hash_table_lookup (cache->line_to_display, cache->cursor_line);
+
+ if (display != NULL)
+ gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
+
+ cache->cursor_line = cursor_line;
+
+ display = g_hash_table_lookup (cache->line_to_display, cache->cursor_line);
+
+ if (display != NULL)
+ gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
+}
diff --git a/gtk/gtktextlinedisplaycacheprivate.h b/gtk/gtktextlinedisplaycacheprivate.h
new file mode 100644
index 0000000000..5639ec2070
--- /dev/null
+++ b/gtk/gtktextlinedisplaycacheprivate.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_TEXT_LINE_DISPLAY_CACHE_PRIVATE_H__
+#define __GTK_TEXT_LINE_DISPLAY_CACHE_PRIVATE_H__
+
+#include "gtktextlayoutprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GtkTextLineDisplayCache GtkTextLineDisplayCache;
+
+GtkTextLineDisplayCache *gtk_text_line_display_cache_new (void);
+void gtk_text_line_display_cache_free (GtkTextLineDisplayCache *cache);
+GtkTextLineDisplay *gtk_text_line_display_cache_get (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout,
+ GtkTextLine *line,
+ gboolean size_only);
+void gtk_text_line_display_cache_delay_eviction (GtkTextLineDisplayCache *cache);
+void gtk_text_line_display_cache_set_cursor_line (GtkTextLineDisplayCache *cache,
+ GtkTextLine *line);
+void gtk_text_line_display_cache_invalidate (GtkTextLineDisplayCache *cache);
+void gtk_text_line_display_cache_invalidate_cursors (GtkTextLineDisplayCache *cache,
+ GtkTextLine *line);
+void gtk_text_line_display_cache_invalidate_display (GtkTextLineDisplayCache *cache,
+ GtkTextLineDisplay *display,
+ gboolean
cursors_only);
+void gtk_text_line_display_cache_invalidate_line (GtkTextLineDisplayCache *cache,
+ GtkTextLine *line);
+void gtk_text_line_display_cache_invalidate_range (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout,
+ const GtkTextIter *begin,
+ const GtkTextIter *end,
+ gboolean
cursors_only);
+void gtk_text_line_display_cache_invalidate_y_range (GtkTextLineDisplayCache *cache,
+ GtkTextLayout *layout,
+ gint y,
+ gint height,
+ gboolean
cursors_only);
+
+G_END_DECLS
+
+#endif /* __GTK_TEXT_LINE_DISPLAY_CACHE_PRIVATE_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 22fe73077f..cdf186af91 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -369,6 +369,7 @@ gtk_public_sources = files([
'gtktexthandle.c',
'gtktextiter.c',
'gtktextlayout.c',
+ 'gtktextlinedisplaycache.c',
'gtktextmark.c',
'gtktextsegment.c',
'gtktexttag.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]