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