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 *)&gtk_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 = &gtk_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 == &gtk_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 != &gtk_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 == &gtk_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 ? &gtk_text_toggle_on_type : &gtk_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 == &gtk_text_toggle_on_type ||
460             seg->type == &gtk_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 == &gtk_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 != &gtk_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