forked from AuroraMiddleware/gtk
35325ea11a
When we invalidate a y_range using the common pattern of y==0 and old_height==new_height, we are generally invalidating the entire buffer. This short-circuits that case to just invalidate the buffer in a faster and more complete form. The problem here appears to be that we can't always calculate the ranges properly to invalidate because validation has not run far enough.
758 lines
21 KiB
C
758 lines
21 KiB
C
/* -*- 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 DEFAULT_MRU_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;
|
|
guint mru_size;
|
|
|
|
#if DEBUG_LINE_DISPLAY_CACHE
|
|
guint log_source;
|
|
int hits;
|
|
int misses;
|
|
int inval;
|
|
int inval_cursors;
|
|
int inval_by_line;
|
|
int inval_by_range;
|
|
int 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);
|
|
ret->mru_size = DEFAULT_MRU_SIZE;
|
|
|
|
#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
|
|
|
|
gtk_text_line_display_cache_invalidate (cache);
|
|
|
|
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;
|
|
int 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 > cache->mru_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);
|
|
g_clear_pointer (&display->node, gsk_render_node_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,
|
|
int 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;
|
|
int cache_y;
|
|
int 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 old height of the range
|
|
* @new_height: the new height of the range
|
|
* @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,
|
|
int y,
|
|
int old_height,
|
|
int new_height,
|
|
gboolean cursors_only)
|
|
{
|
|
GSequenceIter *iter;
|
|
GtkTextBTree *btree;
|
|
|
|
g_assert (cache != NULL);
|
|
g_assert (layout != NULL);
|
|
|
|
STAT_INC (cache->inval_by_y_range);
|
|
|
|
/* A common pattern is to invalidate the whole buffer using y==0 and
|
|
* old_height==new_height. So special case that instead of walking through
|
|
* each display item one at a time.
|
|
*/
|
|
if (y < 0 || (y == 0 && old_height == new_height))
|
|
{
|
|
gtk_text_line_display_cache_invalidate (cache);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
int cache_y;
|
|
int 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);
|
|
}
|
|
|
|
void
|
|
gtk_text_line_display_cache_set_mru_size (GtkTextLineDisplayCache *cache,
|
|
guint mru_size)
|
|
{
|
|
GtkTextLineDisplay *display;
|
|
|
|
g_assert (cache != NULL);
|
|
|
|
if (mru_size == 0)
|
|
mru_size = DEFAULT_MRU_SIZE;
|
|
|
|
if (mru_size != cache->mru_size)
|
|
{
|
|
cache->mru_size = mru_size;
|
|
|
|
while (cache->mru.length > cache->mru_size)
|
|
{
|
|
display = g_queue_peek_tail (&cache->mru);
|
|
|
|
gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
|
|
}
|
|
}
|
|
}
|