1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | /* GTK - The GIMP Toolkit |
3 | * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
4 | * Copyright (C) 2019 Red Hat, Inc. |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include "gtktextbtree.h" |
23 | #include "gtktextbufferprivate.h" |
24 | #include "gtktextiterprivate.h" |
25 | #include "gtktextlinedisplaycacheprivate.h" |
26 | #include "gtkprivate.h" |
27 | |
28 | #define DEFAULT_MRU_SIZE 250 |
29 | #define BLOW_CACHE_TIMEOUT_SEC 20 |
30 | #define DEBUG_LINE_DISPLAY_CACHE 0 |
31 | |
32 | struct _GtkTextLineDisplayCache |
33 | { |
34 | GSequence *sorted_by_line; |
35 | GHashTable *line_to_display; |
36 | GtkTextLine *cursor_line; |
37 | GQueue mru; |
38 | GSource *evict_source; |
39 | guint mru_size; |
40 | |
41 | #if DEBUG_LINE_DISPLAY_CACHE |
42 | guint log_source; |
43 | int hits; |
44 | int misses; |
45 | int inval; |
46 | int inval_cursors; |
47 | int inval_by_line; |
48 | int inval_by_range; |
49 | int inval_by_y_range; |
50 | #endif |
51 | }; |
52 | |
53 | #if DEBUG_LINE_DISPLAY_CACHE |
54 | # define STAT_ADD(val,n) ((val) += n) |
55 | # define STAT_INC(val) STAT_ADD(val,1) |
56 | static gboolean |
57 | dump_stats (gpointer data) |
58 | { |
59 | GtkTextLineDisplayCache *cache = data; |
60 | g_printerr ("%p: size=%u hits=%d misses=%d inval_total=%d " |
61 | "inval_cursors=%d inval_by_line=%d " |
62 | "inval_by_range=%d inval_by_y_range=%d\n" , |
63 | cache, g_hash_table_size (cache->line_to_display), |
64 | cache->hits, cache->misses, |
65 | cache->inval, cache->inval_cursors, |
66 | cache->inval_by_line, cache->inval_by_range, |
67 | cache->inval_by_y_range); |
68 | return G_SOURCE_CONTINUE; |
69 | } |
70 | #else |
71 | # define STAT_ADD(val,n) |
72 | # define STAT_INC(val) |
73 | #endif |
74 | |
75 | GtkTextLineDisplayCache * |
76 | gtk_text_line_display_cache_new (void) |
77 | { |
78 | GtkTextLineDisplayCache *ret; |
79 | |
80 | ret = g_slice_new0 (GtkTextLineDisplayCache); |
81 | ret->sorted_by_line = g_sequence_new (data_destroy: (GDestroyNotify)gtk_text_line_display_unref); |
82 | ret->line_to_display = g_hash_table_new (NULL, NULL); |
83 | ret->mru_size = DEFAULT_MRU_SIZE; |
84 | |
85 | #if DEBUG_LINE_DISPLAY_CACHE |
86 | ret->log_source = g_timeout_add_seconds (1, dump_stats, ret); |
87 | #endif |
88 | |
89 | return g_steal_pointer (&ret); |
90 | } |
91 | |
92 | void |
93 | gtk_text_line_display_cache_free (GtkTextLineDisplayCache *cache) |
94 | { |
95 | #if DEBUG_LINE_DISPLAY_CACHE |
96 | g_clear_handle_id (&cache->log_source, g_source_remove); |
97 | #endif |
98 | |
99 | gtk_text_line_display_cache_invalidate (cache); |
100 | |
101 | g_clear_pointer (&cache->evict_source, g_source_destroy); |
102 | g_clear_pointer (&cache->sorted_by_line, g_sequence_free); |
103 | g_clear_pointer (&cache->line_to_display, g_hash_table_unref); |
104 | g_slice_free (GtkTextLineDisplayCache, cache); |
105 | } |
106 | |
107 | static gboolean |
108 | gtk_text_line_display_cache_blow_cb (gpointer data) |
109 | { |
110 | GtkTextLineDisplayCache *cache = data; |
111 | |
112 | g_assert (cache != NULL); |
113 | |
114 | #if DEBUG_LINE_DISPLAY_CACHE |
115 | g_printerr ("Evicting GtkTextLineDisplayCache\n" ); |
116 | #endif |
117 | |
118 | cache->evict_source = NULL; |
119 | |
120 | gtk_text_line_display_cache_invalidate (cache); |
121 | |
122 | return G_SOURCE_REMOVE; |
123 | } |
124 | |
125 | void |
126 | gtk_text_line_display_cache_delay_eviction (GtkTextLineDisplayCache *cache) |
127 | { |
128 | g_assert (cache != NULL); |
129 | |
130 | if (cache->evict_source != NULL) |
131 | { |
132 | gint64 deadline; |
133 | |
134 | deadline = g_get_monotonic_time () + (BLOW_CACHE_TIMEOUT_SEC * G_USEC_PER_SEC); |
135 | g_source_set_ready_time (source: cache->evict_source, ready_time: deadline); |
136 | } |
137 | else |
138 | { |
139 | guint tag; |
140 | |
141 | tag = g_timeout_add_seconds (BLOW_CACHE_TIMEOUT_SEC, |
142 | function: gtk_text_line_display_cache_blow_cb, |
143 | data: cache); |
144 | cache->evict_source = g_main_context_find_source_by_id (NULL, source_id: tag); |
145 | g_source_set_static_name (cache->evict_source, "[gtk+] gtk_text_line_display_cache_blow_cb" ); |
146 | } |
147 | } |
148 | |
149 | #if DEBUG_LINE_DISPLAY_CACHE |
150 | static void |
151 | check_disposition (GtkTextLineDisplayCache *cache, |
152 | GtkTextLayout *layout) |
153 | { |
154 | GSequenceIter *iter; |
155 | int last = G_MAXUINT; |
156 | |
157 | g_assert (cache != NULL); |
158 | g_assert (cache->sorted_by_line != NULL); |
159 | g_assert (cache->line_to_display != NULL); |
160 | |
161 | for (iter = g_sequence_get_begin_iter (cache->sorted_by_line); |
162 | !g_sequence_iter_is_end (iter); |
163 | iter = g_sequence_iter_next (iter)) |
164 | { |
165 | GtkTextLineDisplay *display = g_sequence_get (iter); |
166 | GtkTextIter text_iter; |
167 | guint line; |
168 | |
169 | gtk_text_layout_get_iter_at_line (layout, &text_iter, display->line, 0); |
170 | line = gtk_text_iter_get_line (&text_iter); |
171 | |
172 | g_assert_cmpint (line, >, last); |
173 | |
174 | last = line; |
175 | } |
176 | } |
177 | #endif |
178 | |
179 | static void |
180 | gtk_text_line_display_cache_take_display (GtkTextLineDisplayCache *cache, |
181 | GtkTextLineDisplay *display, |
182 | GtkTextLayout *layout) |
183 | { |
184 | g_assert (cache != NULL); |
185 | g_assert (display != NULL); |
186 | g_assert (display->line != NULL); |
187 | g_assert (display->cache_iter == NULL); |
188 | g_assert (display->mru_link.data == display); |
189 | g_assert (display->mru_link.prev == NULL); |
190 | g_assert (display->mru_link.next == NULL); |
191 | g_assert (g_hash_table_lookup (cache->line_to_display, display->line) == NULL); |
192 | |
193 | #if DEBUG_LINE_DISPLAY_CACHE |
194 | check_disposition (cache, layout); |
195 | #endif |
196 | |
197 | display->cache_iter = |
198 | g_sequence_insert_sorted (seq: cache->sorted_by_line, |
199 | data: display, |
200 | cmp_func: (GCompareDataFunc) gtk_text_line_display_compare, |
201 | cmp_data: layout); |
202 | g_hash_table_insert (hash_table: cache->line_to_display, key: display->line, value: display); |
203 | g_queue_push_head_link (queue: &cache->mru, link_: &display->mru_link); |
204 | |
205 | /* Cull the cache if we're at capacity */ |
206 | while (cache->mru.length > cache->mru_size) |
207 | { |
208 | display = g_queue_peek_tail (queue: &cache->mru); |
209 | |
210 | gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); |
211 | } |
212 | } |
213 | |
214 | /* |
215 | * gtk_text_line_display_cache_invalidate_display: |
216 | * @cache: a GtkTextLineDisplayCache |
217 | * @display: a GtkTextLineDisplay |
218 | * @cursors_only: if only the cursor positions should be invalidated |
219 | * |
220 | * If @cursors_only is TRUE, then only the cursors are invalidated. Otherwise, |
221 | * @display is removed from the cache. |
222 | * |
223 | * Use this function when you already have access to a display as it reduces |
224 | * some overhead. |
225 | */ |
226 | void |
227 | gtk_text_line_display_cache_invalidate_display (GtkTextLineDisplayCache *cache, |
228 | GtkTextLineDisplay *display, |
229 | gboolean cursors_only) |
230 | { |
231 | g_assert (cache != NULL); |
232 | g_assert (display != NULL); |
233 | g_assert (display->line != NULL); |
234 | |
235 | if (cursors_only) |
236 | { |
237 | g_clear_pointer (&display->cursors, g_array_unref); |
238 | g_clear_pointer (&display->node, gsk_render_node_unref); |
239 | display->cursors_invalid = TRUE; |
240 | display->has_block_cursor = FALSE; |
241 | } |
242 | else |
243 | { |
244 | GSequenceIter *iter = g_steal_pointer (&display->cache_iter); |
245 | |
246 | if (cache->cursor_line == display->line) |
247 | cache->cursor_line = NULL; |
248 | |
249 | g_hash_table_remove (hash_table: cache->line_to_display, key: display->line); |
250 | g_queue_unlink (queue: &cache->mru, link_: &display->mru_link); |
251 | |
252 | if (iter != NULL) |
253 | g_sequence_remove (iter); |
254 | } |
255 | |
256 | STAT_INC (cache->inval); |
257 | } |
258 | |
259 | /* |
260 | * gtk_text_line_display_cache_get: |
261 | * @cache: a `GtkTextLineDisplayCache` |
262 | * @layout: a `GtkTextLayout` |
263 | * @line: a `GtkTextLine` |
264 | * @size_only: if only line sizing is needed |
265 | * |
266 | * Gets a GtkTextLineDisplay for @line. |
267 | * |
268 | * If no cached display exists, a new display will be created. |
269 | * |
270 | * It's possible that calling this function will cause some existing |
271 | * cached displays to be released and destroyed. |
272 | * |
273 | * Returns: (transfer full) (not nullable): a `GtkTextLineDisplay` |
274 | */ |
275 | GtkTextLineDisplay * |
276 | gtk_text_line_display_cache_get (GtkTextLineDisplayCache *cache, |
277 | GtkTextLayout *layout, |
278 | GtkTextLine *line, |
279 | gboolean size_only) |
280 | { |
281 | GtkTextLineDisplay *display; |
282 | |
283 | g_assert (cache != NULL); |
284 | g_assert (layout != NULL); |
285 | g_assert (line != NULL); |
286 | |
287 | display = g_hash_table_lookup (hash_table: cache->line_to_display, key: line); |
288 | |
289 | if (display != NULL) |
290 | { |
291 | if (size_only || !display->size_only) |
292 | { |
293 | STAT_INC (cache->hits); |
294 | |
295 | if (!size_only && display->line == cache->cursor_line) |
296 | gtk_text_layout_update_display_cursors (layout, line: display->line, display); |
297 | |
298 | if (!size_only && display->has_children) |
299 | gtk_text_layout_update_children (layout, display); |
300 | |
301 | /* Move to front of MRU */ |
302 | g_queue_unlink (queue: &cache->mru, link_: &display->mru_link); |
303 | g_queue_push_head_link (queue: &cache->mru, link_: &display->mru_link); |
304 | |
305 | return gtk_text_line_display_ref (display); |
306 | } |
307 | |
308 | /* We need an updated display that includes more than just |
309 | * sizing, so we need to drop this entry and force the layout |
310 | * to create a new one. |
311 | */ |
312 | gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); |
313 | } |
314 | |
315 | STAT_INC (cache->misses); |
316 | |
317 | g_assert (!g_hash_table_lookup (cache->line_to_display, line)); |
318 | |
319 | display = gtk_text_layout_create_display (layout, line, size_only); |
320 | |
321 | g_assert (display != NULL); |
322 | g_assert (display->line == line); |
323 | |
324 | if (!size_only) |
325 | { |
326 | if (line == cache->cursor_line) |
327 | gtk_text_layout_update_display_cursors (layout, line, display); |
328 | |
329 | if (display->has_children) |
330 | gtk_text_layout_update_children (layout, display); |
331 | |
332 | gtk_text_line_display_cache_take_display (cache, |
333 | display: gtk_text_line_display_ref (display), |
334 | layout); |
335 | } |
336 | |
337 | return g_steal_pointer (&display); |
338 | } |
339 | |
340 | void |
341 | gtk_text_line_display_cache_invalidate (GtkTextLineDisplayCache *cache) |
342 | { |
343 | g_assert (cache != NULL); |
344 | g_assert (cache->sorted_by_line != NULL); |
345 | g_assert (cache->line_to_display != NULL); |
346 | |
347 | STAT_ADD (cache->inval, g_hash_table_size (cache->line_to_display)); |
348 | |
349 | cache->cursor_line = NULL; |
350 | |
351 | while (cache->mru.head != NULL) |
352 | { |
353 | GtkTextLineDisplay *display = g_queue_peek_head (queue: &cache->mru); |
354 | |
355 | gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); |
356 | } |
357 | |
358 | g_assert (g_hash_table_size (cache->line_to_display) == 0); |
359 | g_assert (g_sequence_get_length (cache->sorted_by_line) == 0); |
360 | g_assert (cache->mru.length == 0); |
361 | } |
362 | |
363 | void |
364 | gtk_text_line_display_cache_invalidate_cursors (GtkTextLineDisplayCache *cache, |
365 | GtkTextLine *line) |
366 | { |
367 | GtkTextLineDisplay *display; |
368 | |
369 | g_assert (cache != NULL); |
370 | g_assert (line != NULL); |
371 | |
372 | STAT_INC (cache->inval_cursors); |
373 | |
374 | display = g_hash_table_lookup (hash_table: cache->line_to_display, key: line); |
375 | |
376 | if (display != NULL) |
377 | gtk_text_line_display_cache_invalidate_display (cache, display, TRUE); |
378 | } |
379 | |
380 | /* |
381 | * gtk_text_line_display_cache_invalidate_line: |
382 | * @self: a GtkTextLineDisplayCache |
383 | * @line: a GtkTextLine |
384 | * |
385 | * Removes a cached display for @line. |
386 | * |
387 | * Compare to gtk_text_line_display_cache_invalidate_cursors() which |
388 | * only invalidates the cursors for this row. |
389 | */ |
390 | void |
391 | gtk_text_line_display_cache_invalidate_line (GtkTextLineDisplayCache *cache, |
392 | GtkTextLine *line) |
393 | { |
394 | GtkTextLineDisplay *display; |
395 | |
396 | g_assert (cache != NULL); |
397 | g_assert (line != NULL); |
398 | |
399 | display = g_hash_table_lookup (hash_table: cache->line_to_display, key: line); |
400 | |
401 | if (display != NULL) |
402 | gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); |
403 | |
404 | STAT_INC (cache->inval_by_line); |
405 | } |
406 | |
407 | static GSequenceIter * |
408 | find_iter_at_text_iter (GtkTextLineDisplayCache *cache, |
409 | GtkTextLayout *layout, |
410 | const GtkTextIter *iter) |
411 | { |
412 | GSequenceIter *left; |
413 | GSequenceIter *right; |
414 | GSequenceIter *mid; |
415 | GSequenceIter *end; |
416 | GtkTextLine *target; |
417 | guint target_lineno; |
418 | |
419 | g_assert (cache != NULL); |
420 | g_assert (iter != NULL); |
421 | |
422 | if (g_sequence_is_empty (seq: cache->sorted_by_line)) |
423 | return NULL; |
424 | |
425 | /* gtk_text_iter_get_line() will have cached value */ |
426 | target_lineno = gtk_text_iter_get_line (iter); |
427 | target = _gtk_text_iter_get_text_line (iter); |
428 | |
429 | /* Get some iters so we can work with pointer compare */ |
430 | end = g_sequence_get_end_iter (seq: cache->sorted_by_line); |
431 | left = g_sequence_get_begin_iter (seq: cache->sorted_by_line); |
432 | right = g_sequence_iter_prev (iter: end); |
433 | |
434 | /* We already checked for empty above */ |
435 | g_assert (!g_sequence_iter_is_end (left)); |
436 | g_assert (!g_sequence_iter_is_end (right)); |
437 | |
438 | for (;;) |
439 | { |
440 | GtkTextLineDisplay *display; |
441 | guint lineno; |
442 | |
443 | if (left == right) |
444 | mid = left; |
445 | else |
446 | mid = g_sequence_range_get_midpoint (begin: left, end: right); |
447 | |
448 | g_assert (mid != NULL); |
449 | g_assert (!g_sequence_iter_is_end (mid)); |
450 | |
451 | if (mid == end) |
452 | break; |
453 | |
454 | display = g_sequence_get (iter: mid); |
455 | |
456 | g_assert (display != NULL); |
457 | g_assert (display->line != NULL); |
458 | g_assert (display->cache_iter != NULL); |
459 | |
460 | if (target == display->line) |
461 | return mid; |
462 | |
463 | if (right == left) |
464 | break; |
465 | |
466 | lineno = _gtk_text_line_get_number (line: display->line); |
467 | |
468 | if (target_lineno < lineno) |
469 | right = mid; |
470 | else if (target_lineno > lineno) |
471 | left = g_sequence_iter_next (iter: mid); |
472 | else |
473 | g_assert_not_reached (); |
474 | } |
475 | |
476 | return NULL; |
477 | } |
478 | |
479 | |
480 | /* |
481 | * gtk_text_line_display_cache_invalidate_range: |
482 | * @cache: a GtkTextLineDisplayCache |
483 | * @begin: the starting text iter |
484 | * @end: the ending text iter |
485 | * |
486 | * Removes all GtkTextLineDisplay that fall between or including |
487 | * @begin and @end. |
488 | */ |
489 | void |
490 | gtk_text_line_display_cache_invalidate_range (GtkTextLineDisplayCache *cache, |
491 | GtkTextLayout *layout, |
492 | const GtkTextIter *begin, |
493 | const GtkTextIter *end, |
494 | gboolean cursors_only) |
495 | { |
496 | GSequenceIter *begin_iter; |
497 | GSequenceIter *end_iter; |
498 | GSequenceIter *iter; |
499 | |
500 | g_assert (cache != NULL); |
501 | g_assert (layout != NULL); |
502 | g_assert (begin != NULL); |
503 | g_assert (end != NULL); |
504 | |
505 | STAT_INC (cache->inval_by_range); |
506 | |
507 | /* Short-circuit, is_empty() is O(1) */ |
508 | if (g_sequence_is_empty (seq: cache->sorted_by_line)) |
509 | return; |
510 | |
511 | /* gtk_text_iter_order() preserving const */ |
512 | if (gtk_text_iter_compare (lhs: begin, rhs: end) > 0) |
513 | { |
514 | const GtkTextIter *tmp = begin; |
515 | end = begin; |
516 | begin = tmp; |
517 | } |
518 | |
519 | /* Common case, begin/end on same line. Just try to find the line by |
520 | * line number and invalidate it alone. |
521 | */ |
522 | if G_LIKELY (_gtk_text_iter_same_line (begin, end)) |
523 | { |
524 | begin_iter = find_iter_at_text_iter (cache, layout, iter: begin); |
525 | |
526 | if (begin_iter != NULL) |
527 | { |
528 | GtkTextLineDisplay *display = g_sequence_get (iter: begin_iter); |
529 | |
530 | g_assert (display != NULL); |
531 | g_assert (display->line != NULL); |
532 | |
533 | gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only); |
534 | } |
535 | |
536 | return; |
537 | } |
538 | |
539 | /* Find GSequenceIter containing GtkTextLineDisplay that correspond |
540 | * to each of the text positions. |
541 | */ |
542 | begin_iter = find_iter_at_text_iter (cache, layout, iter: begin); |
543 | end_iter = find_iter_at_text_iter (cache, layout, iter: end); |
544 | |
545 | /* Short-circuit if we found nothing */ |
546 | if (begin_iter == NULL && end_iter == NULL) |
547 | return; |
548 | |
549 | /* If nothing matches the end, we need to walk to the end of our |
550 | * cached displays. We know there is a non-zero number of items |
551 | * in the sequence at this point, so we can iter_prev() safely. |
552 | */ |
553 | if (end_iter == NULL) |
554 | end_iter = g_sequence_iter_prev (iter: g_sequence_get_end_iter (seq: cache->sorted_by_line)); |
555 | |
556 | /* If nothing matched the begin, we need to walk starting from |
557 | * the first display we have cached. |
558 | */ |
559 | if (begin_iter == NULL) |
560 | begin_iter = g_sequence_get_begin_iter (seq: cache->sorted_by_line); |
561 | |
562 | iter = begin_iter; |
563 | |
564 | for (;;) |
565 | { |
566 | GtkTextLineDisplay *display = g_sequence_get (iter); |
567 | GSequenceIter *next = g_sequence_iter_next (iter); |
568 | |
569 | gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only); |
570 | |
571 | if (iter == end_iter) |
572 | break; |
573 | |
574 | iter = next; |
575 | } |
576 | } |
577 | |
578 | static GSequenceIter * |
579 | find_iter_at_at_y (GtkTextLineDisplayCache *cache, |
580 | GtkTextLayout *layout, |
581 | int y) |
582 | { |
583 | GtkTextBTree *btree; |
584 | GSequenceIter *left; |
585 | GSequenceIter *right; |
586 | GSequenceIter *mid; |
587 | GSequenceIter *end; |
588 | |
589 | g_assert (cache != NULL); |
590 | g_assert (layout != NULL); |
591 | |
592 | if (g_sequence_is_empty (seq: cache->sorted_by_line)) |
593 | return NULL; |
594 | |
595 | btree = _gtk_text_buffer_get_btree (buffer: layout->buffer); |
596 | |
597 | /* Get some iters so we can work with pointer compare */ |
598 | end = g_sequence_get_end_iter (seq: cache->sorted_by_line); |
599 | left = g_sequence_get_begin_iter (seq: cache->sorted_by_line); |
600 | right = g_sequence_iter_prev (iter: end); |
601 | |
602 | /* We already checked for empty above */ |
603 | g_assert (!g_sequence_iter_is_end (left)); |
604 | g_assert (!g_sequence_iter_is_end (right)); |
605 | |
606 | for (;;) |
607 | { |
608 | GtkTextLineDisplay *display; |
609 | int cache_y; |
610 | int cache_height; |
611 | |
612 | if (left == right) |
613 | mid = left; |
614 | else |
615 | mid = g_sequence_range_get_midpoint (begin: left, end: right); |
616 | |
617 | g_assert (mid != NULL); |
618 | g_assert (!g_sequence_iter_is_end (mid)); |
619 | |
620 | if (mid == end) |
621 | break; |
622 | |
623 | display = g_sequence_get (iter: mid); |
624 | |
625 | g_assert (display != NULL); |
626 | g_assert (display->line != NULL); |
627 | |
628 | cache_y = _gtk_text_btree_find_line_top (tree: btree, line: display->line, view_id: layout); |
629 | cache_height = display->height; |
630 | |
631 | if (y >= cache_y && y <= (cache_y + cache_height)) |
632 | return mid; |
633 | |
634 | if (left == right) |
635 | break; |
636 | |
637 | if (y < cache_y) |
638 | right = mid; |
639 | else if (y > (cache_y + cache_height)) |
640 | left = g_sequence_iter_next (iter: mid); |
641 | else |
642 | g_assert_not_reached (); |
643 | } |
644 | |
645 | return NULL; |
646 | } |
647 | |
648 | /* |
649 | * gtk_text_line_display_cache_invalidate_y_range: |
650 | * @cache: a GtkTextLineDisplayCache |
651 | * @y: the starting Y position |
652 | * @old_height: the old height of the range |
653 | * @new_height: the new height of the range |
654 | * @cursors_only: if only cursors should be invalidated |
655 | * |
656 | * Remove all GtkTextLineDisplay that fall into the range starting |
657 | * from the Y position to Y+Height. |
658 | */ |
659 | void |
660 | gtk_text_line_display_cache_invalidate_y_range (GtkTextLineDisplayCache *cache, |
661 | GtkTextLayout *layout, |
662 | int y, |
663 | int old_height, |
664 | int new_height, |
665 | gboolean cursors_only) |
666 | { |
667 | GSequenceIter *iter; |
668 | GtkTextBTree *btree; |
669 | |
670 | g_assert (cache != NULL); |
671 | g_assert (layout != NULL); |
672 | |
673 | STAT_INC (cache->inval_by_y_range); |
674 | |
675 | /* A common pattern is to invalidate the whole buffer using y==0 and |
676 | * old_height==new_height. So special case that instead of walking through |
677 | * each display item one at a time. |
678 | */ |
679 | if (y < 0 || (y == 0 && old_height == new_height)) |
680 | { |
681 | gtk_text_line_display_cache_invalidate (cache); |
682 | return; |
683 | } |
684 | |
685 | btree = _gtk_text_buffer_get_btree (buffer: layout->buffer); |
686 | iter = find_iter_at_at_y (cache, layout, y); |
687 | |
688 | if (iter == NULL) |
689 | return; |
690 | |
691 | while (!g_sequence_iter_is_end (iter)) |
692 | { |
693 | GtkTextLineDisplay *display; |
694 | int cache_y; |
695 | int cache_height; |
696 | |
697 | display = g_sequence_get (iter); |
698 | iter = g_sequence_iter_next (iter); |
699 | |
700 | cache_y = _gtk_text_btree_find_line_top (tree: btree, line: display->line, view_id: layout); |
701 | cache_height = display->height; |
702 | |
703 | if (cache_y + cache_height >= y && cache_y <= y + old_height) |
704 | { |
705 | gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only); |
706 | |
707 | y += cache_height; |
708 | old_height -= cache_height; |
709 | |
710 | if (old_height > 0) |
711 | continue; |
712 | } |
713 | |
714 | break; |
715 | } |
716 | } |
717 | |
718 | void |
719 | gtk_text_line_display_cache_set_cursor_line (GtkTextLineDisplayCache *cache, |
720 | GtkTextLine *cursor_line) |
721 | { |
722 | GtkTextLineDisplay *display; |
723 | |
724 | g_assert (cache != NULL); |
725 | |
726 | if (cursor_line == cache->cursor_line) |
727 | return; |
728 | |
729 | display = g_hash_table_lookup (hash_table: cache->line_to_display, key: cache->cursor_line); |
730 | |
731 | if (display != NULL) |
732 | gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); |
733 | |
734 | cache->cursor_line = cursor_line; |
735 | |
736 | display = g_hash_table_lookup (hash_table: cache->line_to_display, key: cache->cursor_line); |
737 | |
738 | if (display != NULL) |
739 | gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); |
740 | } |
741 | |
742 | void |
743 | gtk_text_line_display_cache_set_mru_size (GtkTextLineDisplayCache *cache, |
744 | guint mru_size) |
745 | { |
746 | GtkTextLineDisplay *display; |
747 | |
748 | g_assert (cache != NULL); |
749 | |
750 | if (mru_size == 0) |
751 | mru_size = DEFAULT_MRU_SIZE; |
752 | |
753 | if (mru_size != cache->mru_size) |
754 | { |
755 | cache->mru_size = mru_size; |
756 | |
757 | while (cache->mru.length > cache->mru_size) |
758 | { |
759 | display = g_queue_peek_tail (queue: &cache->mru); |
760 | |
761 | gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); |
762 | } |
763 | } |
764 | } |
765 | |