]> Pileus Git - ~andy/gtk/blob - gtk/gtktextsegment.c
add a #error unless you define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API, so
[~andy/gtk] / gtk / gtktextsegment.c
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 "gtktextbtree.h"
55 #include <string.h>
56 #include <ctype.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*
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_flags & GTK_DEBUG_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
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 ("char_segment_check_func: segment has size <= 0");
177     }
178
179   if (strlen (seg->body.chars) != seg->byte_count)
180     {
181       g_error ("char_segment_check_func: 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*
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_malloc (CSEG_SIZE (len));
198   seg->type = &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_flags & GTK_DEBUG_TEXT)
207     char_segment_self_check (seg);
208
209   return seg;
210 }
211
212 GtkTextLineSegment*
213 _gtk_char_segment_new_from_two_strings (const gchar *text1, guint len1,
214                                         const gchar *text2, guint len2)
215 {
216   GtkTextLineSegment *seg;
217
218   g_assert (gtk_text_byte_begins_utf8_char (text1));
219   g_assert (gtk_text_byte_begins_utf8_char (text2));
220
221   seg = g_malloc (CSEG_SIZE (len1+len2));
222   seg->type = &gtk_text_char_type;
223   seg->next = NULL;
224   seg->byte_count = len1 + len2;
225   memcpy (seg->body.chars, text1, len1);
226   memcpy (seg->body.chars + len1, text2, len2);
227   seg->body.chars[len1+len2] = '\0';
228
229   /* In principle this function could probably take chars1 and chars2
230      as args, since it's typically used to merge two char segments */
231   seg->char_count = g_utf8_strlen (seg->body.chars, seg->byte_count);
232
233   if (gtk_debug_flags & GTK_DEBUG_TEXT)
234     char_segment_self_check (seg);
235
236   return seg;
237 }
238
239 /*
240  *--------------------------------------------------------------
241  *
242  * char_segment_split_func --
243  *
244  *      This procedure implements splitting for character segments.
245  *
246  * Results:
247  *      The return value is a pointer to a chain of two segments
248  *      that have the same characters as segPtr except split
249  *      among the two segments.
250  *
251  * Side effects:
252  *      Storage for segPtr is freed.
253  *
254  *--------------------------------------------------------------
255  */
256
257 static GtkTextLineSegment *
258 char_segment_split_func (GtkTextLineSegment *seg, int index)
259 {
260   GtkTextLineSegment *new1, *new2;
261
262   g_assert (index < seg->byte_count);
263
264   if (gtk_debug_flags & GTK_DEBUG_TEXT)
265     {
266       char_segment_self_check (seg);
267     }
268
269   new1 = _gtk_char_segment_new (seg->body.chars, index);
270   new2 = _gtk_char_segment_new (seg->body.chars + index, seg->byte_count - index);
271
272   g_assert (gtk_text_byte_begins_utf8_char (new1->body.chars));
273   g_assert (gtk_text_byte_begins_utf8_char (new2->body.chars));
274   g_assert (new1->byte_count + new2->byte_count == seg->byte_count);
275   g_assert (new1->char_count + new2->char_count == seg->char_count);
276
277   new1->next = new2;
278   new2->next = seg->next;
279
280   if (gtk_debug_flags & GTK_DEBUG_TEXT)
281     {
282       char_segment_self_check (new1);
283       char_segment_self_check (new2);
284     }
285
286   g_free (seg);
287   return new1;
288 }
289
290 /*
291  *--------------------------------------------------------------
292  *
293  * char_segment_cleanup_func --
294  *
295  *      This procedure merges adjacent character segments into
296  *      a single character segment, if possible.
297  *
298  * Results:
299  *      The return value is a pointer to the first segment in
300  *      the (new) list of segments that used to start with segPtr.
301  *
302  * Side effects:
303  *      Storage for the segments may be allocated and freed.
304  *
305  *--------------------------------------------------------------
306  */
307
308         /* ARGSUSED */
309 static GtkTextLineSegment *
310 char_segment_cleanup_func (segPtr, line)
311      GtkTextLineSegment *segPtr;           /* Pointer to first of two adjacent
312                                          * segments to join. */
313      GtkTextLine *line;                /* Line containing segments (not
314                                         * used). */
315 {
316   GtkTextLineSegment *segPtr2, *newPtr;
317
318   if (gtk_debug_flags & GTK_DEBUG_TEXT)
319     char_segment_self_check (segPtr);
320
321   segPtr2 = segPtr->next;
322   if ((segPtr2 == NULL) || (segPtr2->type != &gtk_text_char_type))
323     {
324       return segPtr;
325     }
326
327   newPtr =
328     _gtk_char_segment_new_from_two_strings (segPtr->body.chars, segPtr->byte_count,
329                                             segPtr2->body.chars, segPtr2->byte_count);
330
331   newPtr->next = segPtr2->next;
332
333   if (gtk_debug_flags & GTK_DEBUG_TEXT)
334     char_segment_self_check (newPtr);
335
336   g_free (segPtr);
337   g_free (segPtr2);
338   return newPtr;
339 }
340
341 /*
342  *--------------------------------------------------------------
343  *
344  * char_segment_delete_func --
345  *
346  *      This procedure is invoked to delete a character segment.
347  *
348  * Results:
349  *      Always returns 0 to indicate that the segment was deleted.
350  *
351  * Side effects:
352  *      Storage for the segment is freed.
353  *
354  *--------------------------------------------------------------
355  */
356
357         /* ARGSUSED */
358 static int
359 char_segment_delete_func (segPtr, line, treeGone)
360      GtkTextLineSegment *segPtr;           /* Segment to delete. */
361      GtkTextLine *line;                /* Line containing segment. */
362      int treeGone;                      /* Non-zero means the entire tree is
363                                          * being deleted, so everything must
364                                          * get cleaned up. */
365 {
366   g_free ((char*) segPtr);
367   return 0;
368 }
369
370 /*
371  *--------------------------------------------------------------
372  *
373  * char_segment_check_func --
374  *
375  *      This procedure is invoked to perform consistency checks
376  *      on character segments.
377  *
378  * Results:
379  *      None.
380  *
381  * Side effects:
382  *      If the segment isn't inconsistent then the procedure
383  *      g_errors.
384  *
385  *--------------------------------------------------------------
386  */
387
388         /* ARGSUSED */
389 static void
390 char_segment_check_func (segPtr, line)
391      GtkTextLineSegment *segPtr;           /* Segment to check. */
392      GtkTextLine *line;                /* Line containing segment. */
393 {
394   char_segment_self_check (segPtr);
395
396   if (segPtr->next != NULL)
397     {
398       if (segPtr->next->type == &gtk_text_char_type)
399         {
400           g_error ("char_segment_check_func: adjacent character segments weren't merged");
401         }
402     }
403 }
404
405 GtkTextLineSegment*
406 _gtk_toggle_segment_new (GtkTextTagInfo *info, gboolean on)
407 {
408   GtkTextLineSegment *seg;
409
410   seg = g_malloc (TSEG_SIZE);
411
412   seg->type = on ? &gtk_text_toggle_on_type : &gtk_text_toggle_off_type;
413
414   seg->next = NULL;
415
416   seg->byte_count = 0;
417   seg->char_count = 0;
418
419   seg->body.toggle.info = info;
420   seg->body.toggle.inNodeCounts = 0;
421
422   return seg;
423 }
424
425 /*
426  *--------------------------------------------------------------
427  *
428  * toggle_segment_delete_func --
429  *
430  *      This procedure is invoked to delete toggle segments.
431  *
432  * Results:
433  *      Returns 1 to indicate that the segment may not be deleted,
434  *      unless the entire B-tree is going away.
435  *
436  * Side effects:
437  *      If the tree is going away then the toggle's memory is
438  *      freed;  otherwise the toggle counts in GtkTextBTreeNodes above the
439  *      segment get updated.
440  *
441  *--------------------------------------------------------------
442  */
443
444 static int
445 toggle_segment_delete_func (segPtr, line, treeGone)
446      GtkTextLineSegment *segPtr;           /* Segment to check. */
447      GtkTextLine *line;                /* Line containing segment. */
448      int treeGone;                      /* Non-zero means the entire tree is
449                                          * being deleted, so everything must
450                                          * get cleaned up. */
451 {
452   if (treeGone)
453     {
454       g_free ((char *) segPtr);
455       return 0;
456     }
457
458   /*
459    * This toggle is in the middle of a range of characters that's
460    * being deleted.  Refuse to die.  We'll be moved to the end of
461    * the deleted range and our cleanup procedure will be called
462    * later.  Decrement GtkTextBTreeNode toggle counts here, and set a flag
463    * so we'll re-increment them in the cleanup procedure.
464    */
465
466   if (segPtr->body.toggle.inNodeCounts)
467     {
468       _gtk_change_node_toggle_count (line->parent,
469                                      segPtr->body.toggle.info, -1);
470       segPtr->body.toggle.inNodeCounts = 0;
471     }
472   return 1;
473 }
474
475 /*
476  *--------------------------------------------------------------
477  *
478  * toggle_segment_cleanup_func --
479  *
480  *      This procedure is called when a toggle is part of a line that's
481  *      been modified in some way.  It's invoked after the
482  *      modifications are complete.
483  *
484  * Results:
485  *      The return value is the head segment in a new list
486  *      that is to replace the tail of the line that used to
487  *      start at segPtr.  This allows the procedure to delete
488  *      or modify segPtr.
489  *
490  * Side effects:
491  *      Toggle counts in the GtkTextBTreeNodes above the new line will be
492  *      updated if they're not already.  Toggles may be collapsed
493  *      if there are duplicate toggles at the same position.
494  *
495  *--------------------------------------------------------------
496  */
497
498 static GtkTextLineSegment *
499 toggle_segment_cleanup_func (segPtr, line)
500      GtkTextLineSegment *segPtr;   /* Segment to check. */
501      GtkTextLine *line;        /* Line that now contains segment. */
502 {
503   GtkTextLineSegment *segPtr2, *prevPtr;
504   int counts;
505
506   /*
507    * If this is a toggle-off segment, look ahead through the next
508    * segments to see if there's a toggle-on segment for the same tag
509    * before any segments with non-zero size.  If so then the two
510    * toggles cancel each other;  remove them both.
511    */
512
513   if (segPtr->type == &gtk_text_toggle_off_type)
514     {
515       for (prevPtr = segPtr, segPtr2 = prevPtr->next;
516            (segPtr2 != NULL) && (segPtr2->byte_count == 0);
517            prevPtr = segPtr2, segPtr2 = prevPtr->next)
518         {
519           if (segPtr2->type != &gtk_text_toggle_on_type)
520             {
521               continue;
522             }
523           if (segPtr2->body.toggle.info != segPtr->body.toggle.info)
524             {
525               continue;
526             }
527           counts = segPtr->body.toggle.inNodeCounts
528             + segPtr2->body.toggle.inNodeCounts;
529           if (counts != 0)
530             {
531               _gtk_change_node_toggle_count (line->parent,
532                                              segPtr->body.toggle.info, -counts);
533             }
534           prevPtr->next = segPtr2->next;
535           g_free ((char *) segPtr2);
536           segPtr2 = segPtr->next;
537           g_free ((char *) segPtr);
538           return segPtr2;
539         }
540     }
541
542   if (!segPtr->body.toggle.inNodeCounts)
543     {
544       _gtk_change_node_toggle_count (line->parent,
545                                      segPtr->body.toggle.info, 1);
546       segPtr->body.toggle.inNodeCounts = 1;
547     }
548   return segPtr;
549 }
550
551 /*
552  *--------------------------------------------------------------
553  *
554  * toggle_segment_line_change_func --
555  *
556  *      This procedure is invoked when a toggle segment is about
557  *      to move from one line to another.
558  *
559  * Results:
560  *      None.
561  *
562  * Side effects:
563  *      Toggle counts are decremented in the GtkTextBTreeNodes above the line.
564  *
565  *--------------------------------------------------------------
566  */
567
568 static void
569 toggle_segment_line_change_func (segPtr, line)
570      GtkTextLineSegment *segPtr;   /* Segment to check. */
571      GtkTextLine *line;        /* Line that used to contain segment. */
572 {
573   if (segPtr->body.toggle.inNodeCounts)
574     {
575       _gtk_change_node_toggle_count (line->parent,
576                                      segPtr->body.toggle.info, -1);
577       segPtr->body.toggle.inNodeCounts = 0;
578     }
579 }
580
581 /*
582  * Virtual tables
583  */
584
585
586 GtkTextLineSegmentClass gtk_text_char_type = {
587   "character",                          /* name */
588   0,                                            /* leftGravity */
589   char_segment_split_func,                              /* splitFunc */
590   char_segment_delete_func,                             /* deleteFunc */
591   char_segment_cleanup_func,                            /* cleanupFunc */
592   NULL,         /* lineChangeFunc */
593   char_segment_check_func                               /* checkFunc */
594 };
595
596 /*
597  * Type record for segments marking the beginning of a tagged
598  * range:
599  */
600
601 GtkTextLineSegmentClass gtk_text_toggle_on_type = {
602   "toggleOn",                                   /* name */
603   0,                                            /* leftGravity */
604   NULL,                 /* splitFunc */
605   toggle_segment_delete_func,                           /* deleteFunc */
606   toggle_segment_cleanup_func,                          /* cleanupFunc */
607   toggle_segment_line_change_func,                      /* lineChangeFunc */
608   _gtk_toggle_segment_check_func                        /* checkFunc */
609 };
610
611 /*
612  * Type record for segments marking the end of a tagged
613  * range:
614  */
615
616 GtkTextLineSegmentClass gtk_text_toggle_off_type = {
617   "toggleOff",                          /* name */
618   1,                                            /* leftGravity */
619   NULL,                 /* splitFunc */
620   toggle_segment_delete_func,                           /* deleteFunc */
621   toggle_segment_cleanup_func,                          /* cleanupFunc */
622   toggle_segment_line_change_func,                      /* lineChangeFunc */
623   _gtk_toggle_segment_check_func                        /* checkFunc */
624 };