forked from AuroraMiddleware/gtk
5e49da1d73
This tries to estimate the number of visible rows in a textview based on the default text size and then tunes the GtkTextLineDisplayCache to keep 3*n_rows entries in the cache. This was found imperically to be near the right cache size. In most cases, this is less than the number of items we cache now. However, in some cases, such as the "overview map" from GtkSourceView, it allows us to reach a higher value such as 1000+. This is needed to keep scrolling smooth on the larger view sizes. With this patch, a HiDPI system with a GtkSourceView and GtkSourceMap from the GTK 4 port can perform smooth scrolling simultaneously.
745 lines
21 KiB
C
745 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;
|
|
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);
|
|
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;
|
|
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 > 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);
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|