1/*
2 * gtktextsegment.c --
3 *
4 * Code for segments in general, and toggle/char segments in particular.
5 *
6 * Copyright (c) 1992-1994 The Regents of the University of California.
7 * Copyright (c) 1994-1995 Sun Microsystems, Inc.
8 * Copyright (c) 2000 Red Hat, Inc.
9 * Tk -> Gtk port by Havoc Pennington <hp@redhat.com>
10 *
11 * This software is copyrighted by the Regents of the University of
12 * California, Sun Microsystems, Inc., and other parties. The
13 * following terms apply to all files associated with the software
14 * unless explicitly disclaimed in individual files.
15 *
16 * The authors hereby grant permission to use, copy, modify,
17 * distribute, and license this software and its documentation for any
18 * purpose, provided that existing copyright notices are retained in
19 * all copies and that this notice is included verbatim in any
20 * distributions. No written agreement, license, or royalty fee is
21 * required for any of the authorized uses. Modifications to this
22 * software may be copyrighted by their authors and need not follow
23 * the licensing terms described here, provided that the new terms are
24 * clearly indicated on the first page of each file where they apply.
25 *
26 * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY
27 * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
28 * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION,
29 * OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
33 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
34 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
35 * NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
36 * AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
37 * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
38 *
39 * GOVERNMENT USE: If you are acquiring this software on behalf of the
40 * U.S. government, the Government shall have only "Restricted Rights"
41 * in the software and related documentation as defined in the Federal
42 * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
43 * are acquiring the software on behalf of the Department of Defense,
44 * the software shall be classified as "Commercial Computer Software"
45 * and the Government shall have only "Restricted Rights" as defined
46 * in Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the
47 * foregoing, the authors grant the U.S. Government and others acting
48 * in its behalf permission to use and distribute the software in
49 * accordance with the terms specified in this license.
50 *
51 */
52
53#include "config.h"
54#include "gtktextbtree.h"
55#include <string.h>
56#include <stdlib.h>
57#include <stdio.h>
58#include "gtktexttag.h"
59#include "gtktexttagtable.h"
60#include "gtktextlayoutprivate.h"
61#include "gtktextiterprivate.h"
62#include "gtkdebug.h"
63
64/*
65 *--------------------------------------------------------------
66 *
67 * split_segment --
68 *
69 * This procedure is called before adding or deleting
70 * segments. It does three things: (a) it finds the segment
71 * containing iter; (b) if there are several such
72 * segments (because some segments have zero length) then
73 * it picks the first segment that does not have left
74 * gravity; (c) if the index refers to the middle of
75 * a segment then it splits the segment so that the
76 * index now refers to the beginning of a segment.
77 *
78 * Results:
79 * The return value is a pointer to the segment just
80 * before the segment corresponding to iter (as
81 * described above). If the segment corresponding to
82 * iter is the first in its line then the return
83 * value is NULL.
84 *
85 * Side effects:
86 * The segment referred to by iter is split unless
87 * iter refers to its first character.
88 *
89 *--------------------------------------------------------------
90 */
91
92GtkTextLineSegment*
93gtk_text_line_segment_split (const GtkTextIter *iter)
94{
95 GtkTextLineSegment *prev, *seg;
96 GtkTextBTree *tree;
97 GtkTextLine *line;
98 int count;
99
100 line = _gtk_text_iter_get_text_line (iter);
101 tree = _gtk_text_iter_get_btree (iter);
102
103 count = gtk_text_iter_get_line_index (iter);
104
105 if (GTK_DEBUG_CHECK (TEXT))
106 _gtk_text_iter_check (iter);
107
108 prev = NULL;
109 seg = line->segments;
110
111 while (seg != NULL)
112 {
113 if (seg->byte_count > count)
114 {
115 if (count == 0)
116 {
117 return prev;
118 }
119 else
120 {
121 g_assert (count != seg->byte_count);
122 g_assert (seg->byte_count > 0);
123
124 _gtk_text_btree_segments_changed (tree);
125
126 seg = (*seg->type->splitFunc)(seg, count);
127
128 if (prev == NULL)
129 line->segments = seg;
130 else
131 prev->next = seg;
132
133 return seg;
134 }
135 }
136 else if ((seg->byte_count == 0) && (count == 0)
137 && !seg->type->leftGravity)
138 {
139 return prev;
140 }
141
142 count -= seg->byte_count;
143 prev = seg;
144 seg = seg->next;
145 }
146 g_error ("split_segment reached end of line!");
147 return NULL;
148}
149
150
151/*
152 * Macros that determine how much space to allocate for new segments:
153 */
154
155#define CSEG_SIZE(chars) ((unsigned) (G_STRUCT_OFFSET (GtkTextLineSegment, body) \
156 + 1 + (chars)))
157#define TSEG_SIZE ((unsigned) (G_STRUCT_OFFSET (GtkTextLineSegment, body) \
158 + sizeof (GtkTextToggleBody)))
159
160/*
161 * Type functions
162 */
163
164static void
165char_segment_self_check (GtkTextLineSegment *seg)
166{
167 /* This function checks the segment itself, but doesn't
168 assume the segment has been validly inserted into
169 the btree. */
170
171 g_assert (seg != NULL);
172
173 if (seg->byte_count <= 0)
174 {
175 g_error ("segment has size <= 0");
176 }
177
178 if (strlen (s: seg->body.chars) != seg->byte_count)
179 {
180 g_error ("segment has wrong size");
181 }
182
183 if (g_utf8_strlen (p: seg->body.chars, max: seg->byte_count) != seg->char_count)
184 {
185 g_error ("char segment has wrong character count");
186 }
187}
188
189GtkTextLineSegment*
190_gtk_char_segment_new (const char *text, guint len)
191{
192 GtkTextLineSegment *seg;
193
194 g_assert (gtk_text_byte_begins_utf8_char (text));
195
196 seg = g_slice_alloc (CSEG_SIZE (len));
197 seg->type = (GtkTextLineSegmentClass *)&gtk_text_char_type;
198 seg->next = NULL;
199 seg->byte_count = len;
200 memcpy (dest: seg->body.chars, src: text, n: len);
201 seg->body.chars[len] = '\0';
202
203 seg->char_count = g_utf8_strlen (p: seg->body.chars, max: seg->byte_count);
204
205 if (GTK_DEBUG_CHECK (TEXT))
206 char_segment_self_check (seg);
207
208 return seg;
209}
210
211GtkTextLineSegment*
212_gtk_char_segment_new_from_two_strings (const char *text1,
213 guint len1,
214 guint chars1,
215 const char *text2,
216 guint len2,
217 guint chars2)
218{
219 GtkTextLineSegment *seg;
220
221 g_assert (gtk_text_byte_begins_utf8_char (text1));
222 g_assert (gtk_text_byte_begins_utf8_char (text2));
223
224 seg = g_slice_alloc (CSEG_SIZE (len1+len2));
225 seg->type = &gtk_text_char_type;
226 seg->next = NULL;
227 seg->byte_count = len1 + len2;
228 memcpy (dest: seg->body.chars, src: text1, n: len1);
229 memcpy (dest: seg->body.chars + len1, src: text2, n: len2);
230 seg->body.chars[len1+len2] = '\0';
231
232 seg->char_count = chars1 + chars2;
233
234 if (GTK_DEBUG_CHECK (TEXT))
235 char_segment_self_check (seg);
236
237 return seg;
238}
239
240static void
241_gtk_char_segment_free (GtkTextLineSegment *seg)
242{
243 if (seg == NULL)
244 return;
245
246 g_assert (seg->type == &gtk_text_char_type);
247
248 g_slice_free1 (CSEG_SIZE (seg->byte_count), mem_block: seg);
249}
250
251/*
252 *--------------------------------------------------------------
253 *
254 * char_segment_split_func --
255 *
256 * This procedure implements splitting for character segments.
257 *
258 * Results:
259 * The return value is a pointer to a chain of two segments
260 * that have the same characters as segPtr except split
261 * among the two segments.
262 *
263 * Side effects:
264 * Storage for segPtr is freed.
265 *
266 *--------------------------------------------------------------
267 */
268
269static GtkTextLineSegment *
270char_segment_split_func (GtkTextLineSegment *seg, int index)
271{
272 GtkTextLineSegment *new1, *new2;
273
274 g_assert (index < seg->byte_count);
275
276 if (GTK_DEBUG_CHECK (TEXT))
277 {
278 char_segment_self_check (seg);
279 }
280
281 new1 = _gtk_char_segment_new (text: seg->body.chars, len: index);
282 new2 = _gtk_char_segment_new (text: seg->body.chars + index, len: seg->byte_count - index);
283
284 g_assert (gtk_text_byte_begins_utf8_char (new1->body.chars));
285 g_assert (gtk_text_byte_begins_utf8_char (new2->body.chars));
286 g_assert (new1->byte_count + new2->byte_count == seg->byte_count);
287 g_assert (new1->char_count + new2->char_count == seg->char_count);
288
289 new1->next = new2;
290 new2->next = seg->next;
291
292 if (GTK_DEBUG_CHECK (TEXT))
293 {
294 char_segment_self_check (seg: new1);
295 char_segment_self_check (seg: new2);
296 }
297
298 _gtk_char_segment_free (seg);
299 return new1;
300}
301
302/*
303 *--------------------------------------------------------------
304 *
305 * char_segment_cleanup_func --
306 *
307 * This procedure merges adjacent character segments into
308 * a single character segment, if possible.
309 *
310 * Arguments:
311 * segPtr: Pointer to the first of two adjacent segments to
312 * join.
313 * line: Line containing segments (not used).
314 *
315 * Results:
316 * The return value is a pointer to the first segment in
317 * the (new) list of segments that used to start with segPtr.
318 *
319 * Side effects:
320 * Storage for the segments may be allocated and freed.
321 *
322 *--------------------------------------------------------------
323 */
324
325 /* ARGSUSED */
326static GtkTextLineSegment *
327char_segment_cleanup_func (GtkTextLineSegment *segPtr, GtkTextLine *line)
328{
329 GtkTextLineSegment *segPtr2, *newPtr;
330
331 if (GTK_DEBUG_CHECK (TEXT))
332 char_segment_self_check (seg: segPtr);
333
334 segPtr2 = segPtr->next;
335 if ((segPtr2 == NULL) || (segPtr2->type != &gtk_text_char_type))
336 {
337 return segPtr;
338 }
339
340 newPtr =
341 _gtk_char_segment_new_from_two_strings (text1: segPtr->body.chars,
342 len1: segPtr->byte_count,
343 chars1: segPtr->char_count,
344 text2: segPtr2->body.chars,
345 len2: segPtr2->byte_count,
346 chars2: segPtr2->char_count);
347
348 newPtr->next = segPtr2->next;
349
350 if (GTK_DEBUG_CHECK (TEXT))
351 char_segment_self_check (seg: newPtr);
352
353 _gtk_char_segment_free (seg: segPtr);
354 _gtk_char_segment_free (seg: segPtr2);
355 return newPtr;
356}
357
358/*
359 *--------------------------------------------------------------
360 *
361 * char_segment_delete_func --
362 *
363 * This procedure is invoked to delete a character segment.
364 *
365 * Arguments:
366 * segPtr : Segment to delete
367 * line : Line containing segment
368 * treeGone : Non-zero means the entire tree is being
369 * deleted, so everything must get cleaned up.
370 *
371 * Results:
372 * Always returns 0 to indicate that the segment was deleted.
373 *
374 * Side effects:
375 * Storage for the segment is freed.
376 *
377 *--------------------------------------------------------------
378 */
379
380 /* ARGSUSED */
381static int
382char_segment_delete_func (GtkTextLineSegment *segPtr, GtkTextLine *line, int treeGone)
383{
384 _gtk_char_segment_free (seg: segPtr);
385 return 0;
386}
387
388/*
389 *--------------------------------------------------------------
390 *
391 * char_segment_check_func --
392 *
393 * This procedure is invoked to perform consistency checks
394 * on character segments.
395 *
396 * Arguments:
397 * segPtr : Segment to check
398 * line : Line containing segment
399 *
400 * Results:
401 * None.
402 *
403 * Side effects:
404 * If the segment isn’t inconsistent then the procedure
405 * g_errors.
406 *
407 *--------------------------------------------------------------
408 */
409
410 /* ARGSUSED */
411static void
412char_segment_check_func (GtkTextLineSegment *segPtr, GtkTextLine *line)
413{
414 char_segment_self_check (seg: segPtr);
415
416 if (segPtr->next != NULL)
417 {
418 if (segPtr->next->type == &gtk_text_char_type)
419 {
420 g_error ("adjacent character segments weren't merged");
421 }
422 }
423}
424
425GtkTextLineSegment*
426_gtk_toggle_segment_new (GtkTextTagInfo *info, gboolean on)
427{
428 /* gcc-11 issues a diagnostic here because the size allocated
429 for SEG does not cover the entire size of a GtkTextLineSegment
430 and gcc has no way to know that the union will only be used
431 for limited types and the additional space is not needed. */
432#pragma GCC diagnostic push
433#pragma GCC diagnostic ignored "-Warray-bounds"
434 GtkTextLineSegment *seg;
435
436 seg = g_slice_alloc (TSEG_SIZE);
437
438 seg->type = on ? &gtk_text_toggle_on_type : &gtk_text_toggle_off_type;
439
440 seg->next = NULL;
441
442 seg->byte_count = 0;
443 seg->char_count = 0;
444
445 seg->body.toggle.info = info;
446 seg->body.toggle.inNodeCounts = 0;
447
448 return seg;
449#pragma GCC diagnostic pop
450}
451
452void
453_gtk_toggle_segment_free (GtkTextLineSegment *seg)
454{
455 if (seg == NULL)
456 return;
457
458 g_assert (seg->type == &gtk_text_toggle_on_type ||
459 seg->type == &gtk_text_toggle_off_type);
460
461 g_slice_free1 (TSEG_SIZE, mem_block: seg);
462}
463
464/*
465 *--------------------------------------------------------------
466 *
467 * toggle_segment_delete_func --
468 *
469 * This procedure is invoked to delete toggle segments.
470 *
471 * Arguments:
472 * segPtr : Segment to check
473 * line : Line containing segment
474 * treeGone : Non-zero means the entire tree is being
475 * deleted so everything must get cleaned up
476 *
477 * Results:
478 * Returns 1 to indicate that the segment may not be deleted,
479 * unless the entire B-tree is going away.
480 *
481 * Side effects:
482 * If the tree is going away then the toggle’s memory is
483 * freed; otherwise the toggle counts in GtkTextBTreeNodes above the
484 * segment get updated.
485 *
486 *--------------------------------------------------------------
487 */
488
489static int
490toggle_segment_delete_func (GtkTextLineSegment *segPtr, GtkTextLine *line, int treeGone)
491{
492 if (treeGone)
493 {
494 _gtk_toggle_segment_free (seg: segPtr);
495 return 0;
496 }
497
498 /*
499 * This toggle is in the middle of a range of characters that's
500 * being deleted. Refuse to die. We'll be moved to the end of
501 * the deleted range and our cleanup procedure will be called
502 * later. Decrement GtkTextBTreeNode toggle counts here, and set a flag
503 * so we'll re-increment them in the cleanup procedure.
504 */
505
506 if (segPtr->body.toggle.inNodeCounts)
507 {
508 _gtk_change_node_toggle_count (node: line->parent,
509 info: segPtr->body.toggle.info, delta: -1);
510 segPtr->body.toggle.inNodeCounts = 0;
511 }
512 return 1;
513}
514
515/*
516 *--------------------------------------------------------------
517 *
518 * toggle_segment_cleanup_func --
519 *
520 * This procedure is called when a toggle is part of a line that's
521 * been modified in some way. It’s invoked after the
522 * modifications are complete.
523 *
524 * Arguments:
525 * segPtr : Segment to check
526 * line : Line that now contains segment
527 *
528 * Results:
529 * The return value is the head segment in a new list
530 * that is to replace the tail of the line that used to
531 * start at segPtr. This allows the procedure to delete
532 * or modify segPtr.
533 *
534 * Side effects:
535 * Toggle counts in the GtkTextBTreeNodes above the new line will be
536 * updated if they’re not already. Toggles may be collapsed
537 * if there are duplicate toggles at the same position.
538 *
539 *--------------------------------------------------------------
540 */
541
542static GtkTextLineSegment *
543toggle_segment_cleanup_func (GtkTextLineSegment *segPtr, GtkTextLine *line)
544{
545 GtkTextLineSegment *segPtr2, *prevPtr;
546 int counts;
547
548 /*
549 * If this is a toggle-off segment, look ahead through the next
550 * segments to see if there's a toggle-on segment for the same tag
551 * before any segments with non-zero size. If so then the two
552 * toggles cancel each other; remove them both.
553 */
554
555 if (segPtr->type == &gtk_text_toggle_off_type)
556 {
557 for (prevPtr = segPtr, segPtr2 = prevPtr->next;
558 (segPtr2 != NULL) && (segPtr2->byte_count == 0);
559 prevPtr = segPtr2, segPtr2 = prevPtr->next)
560 {
561 if (segPtr2->type != &gtk_text_toggle_on_type)
562 {
563 continue;
564 }
565 if (segPtr2->body.toggle.info != segPtr->body.toggle.info)
566 {
567 continue;
568 }
569 counts = segPtr->body.toggle.inNodeCounts
570 + segPtr2->body.toggle.inNodeCounts;
571 if (counts != 0)
572 {
573 _gtk_change_node_toggle_count (node: line->parent,
574 info: segPtr->body.toggle.info, delta: -counts);
575 }
576 prevPtr->next = segPtr2->next;
577 _gtk_toggle_segment_free (seg: segPtr2);
578 segPtr2 = segPtr->next;
579 _gtk_toggle_segment_free (seg: segPtr);
580 return segPtr2;
581 }
582 }
583
584 if (!segPtr->body.toggle.inNodeCounts)
585 {
586 _gtk_change_node_toggle_count (node: line->parent,
587 info: segPtr->body.toggle.info, delta: 1);
588 segPtr->body.toggle.inNodeCounts = 1;
589 }
590 return segPtr;
591}
592
593/*
594 *--------------------------------------------------------------
595 *
596 * toggle_segment_line_change_func --
597 *
598 * This procedure is invoked when a toggle segment is about
599 * to move from one line to another.
600 *
601 * Arguments:
602 * segPtr : Segment to check
603 * line : Line that used to contain segment
604 *
605 * Results:
606 * None.
607 *
608 * Side effects:
609 * Toggle counts are decremented in the GtkTextBTreeNodes above the line.
610 *
611 *--------------------------------------------------------------
612 */
613
614static void
615toggle_segment_line_change_func (GtkTextLineSegment *segPtr, GtkTextLine *line)
616{
617 if (segPtr->body.toggle.inNodeCounts)
618 {
619 _gtk_change_node_toggle_count (node: line->parent,
620 info: segPtr->body.toggle.info, delta: -1);
621 segPtr->body.toggle.inNodeCounts = 0;
622 }
623}
624
625/*
626 * Virtual tables
627 */
628
629
630const GtkTextLineSegmentClass gtk_text_char_type = {
631 "character", /* name */
632 0, /* leftGravity */
633 char_segment_split_func, /* splitFunc */
634 char_segment_delete_func, /* deleteFunc */
635 char_segment_cleanup_func, /* cleanupFunc */
636 NULL, /* lineChangeFunc */
637 char_segment_check_func /* checkFunc */
638};
639
640/*
641 * Type record for segments marking the beginning of a tagged
642 * range:
643 */
644
645const GtkTextLineSegmentClass gtk_text_toggle_on_type = {
646 "toggleOn", /* name */
647 0, /* leftGravity */
648 NULL, /* splitFunc */
649 toggle_segment_delete_func, /* deleteFunc */
650 toggle_segment_cleanup_func, /* cleanupFunc */
651 toggle_segment_line_change_func, /* lineChangeFunc */
652 _gtk_toggle_segment_check_func /* checkFunc */
653};
654
655/*
656 * Type record for segments marking the end of a tagged
657 * range:
658 */
659
660const GtkTextLineSegmentClass gtk_text_toggle_off_type = {
661 "toggleOff", /* name */
662 1, /* leftGravity */
663 NULL, /* splitFunc */
664 toggle_segment_delete_func, /* deleteFunc */
665 toggle_segment_cleanup_func, /* cleanupFunc */
666 toggle_segment_line_change_func, /* lineChangeFunc */
667 _gtk_toggle_segment_check_func /* checkFunc */
668};
669

source code of gtk/gtk/gtktextsegment.c