/* * gtktextsegment.c -- * * Code for segments in general, and toggle/char segments in particular. * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * Copyright (c) 2000 Red Hat, Inc. * Tk -> Gtk port by Havoc Pennington * * This software is copyrighted by the Regents of the University of * California, Sun Microsystems, Inc., and other parties. The * following terms apply to all files associated with the software * unless explicitly disclaimed in individual files. * * The authors hereby grant permission to use, copy, modify, * distribute, and license this software and its documentation for any * purpose, provided that existing copyright notices are retained in * all copies and that this notice is included verbatim in any * distributions. No written agreement, license, or royalty fee is * required for any of the authorized uses. Modifications to this * software may be copyrighted by their authors and need not follow * the licensing terms described here, provided that the new terms are * clearly indicated on the first page of each file where they apply. * * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, * OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND * NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, * AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * GOVERNMENT USE: If you are acquiring this software on behalf of the * U.S. government, the Government shall have only "Restricted Rights" * in the software and related documentation as defined in the Federal * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you * are acquiring the software on behalf of the Department of Defense, * the software shall be classified as "Commercial Computer Software" * and the Government shall have only "Restricted Rights" as defined * in Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the * foregoing, the authors grant the U.S. Government and others acting * in its behalf permission to use and distribute the software in * accordance with the terms specified in this license. * */ #include "config.h" #include "gtktextbtreeprivate.h" #include #include #include #include "gtktexttag.h" #include "gtktexttagtable.h" #include "gtktextlayoutprivate.h" #include "gtktextiterprivate.h" #include "gtkdebug.h" /* *-------------------------------------------------------------- * * split_segment -- * * This procedure is called before adding or deleting * segments. It does three things: (a) it finds the segment * containing iter; (b) if there are several such * segments (because some segments have zero length) then * it picks the first segment that does not have left * gravity; (c) if the index refers to the middle of * a segment then it splits the segment so that the * index now refers to the beginning of a segment. * * Results: * The return value is a pointer to the segment just * before the segment corresponding to iter (as * described above). If the segment corresponding to * iter is the first in its line then the return * value is NULL. * * Side effects: * The segment referred to by iter is split unless * iter refers to its first character. * *-------------------------------------------------------------- */ GtkTextLineSegment* gtk_text_line_segment_split (const GtkTextIter *iter) { GtkTextLineSegment *prev, *seg; GtkTextBTree *tree; GtkTextLine *line; int count; line = _gtk_text_iter_get_text_line (iter); tree = _gtk_text_iter_get_btree (iter); count = gtk_text_iter_get_line_index (iter); if (GTK_DEBUG_CHECK (TEXT)) _gtk_text_iter_check (iter); prev = NULL; seg = line->segments; while (seg != NULL) { if (seg->byte_count > count) { if (count == 0) { return prev; } else { g_assert (count != seg->byte_count); g_assert (seg->byte_count > 0); _gtk_text_btree_segments_changed (tree); seg = (*seg->type->splitFunc)(seg, count); if (prev == NULL) line->segments = seg; else prev->next = seg; return seg; } } else if ((seg->byte_count == 0) && (count == 0) && !seg->type->leftGravity) { return prev; } count -= seg->byte_count; prev = seg; seg = seg->next; } g_error ("split_segment reached end of line!"); return NULL; } /* * Macros that determine how much space to allocate for new segments: */ #define CSEG_SIZE(chars) ((unsigned) (G_STRUCT_OFFSET (GtkTextLineSegment, body) \ + 1 + (chars))) #define TSEG_SIZE ((unsigned) (G_STRUCT_OFFSET (GtkTextLineSegment, body) \ + sizeof (GtkTextToggleBody))) /* * Type functions */ static void char_segment_self_check (GtkTextLineSegment *seg) { /* This function checks the segment itself, but doesn't assume the segment has been validly inserted into the btree. */ g_assert (seg != NULL); if (seg->byte_count <= 0) { g_error ("segment has size <= 0"); } if (strlen (seg->body.chars) != seg->byte_count) { g_error ("segment has wrong size"); } if (g_utf8_strlen (seg->body.chars, seg->byte_count) != seg->char_count) { g_error ("char segment has wrong character count"); } } GtkTextLineSegment* _gtk_char_segment_new (const char *text, guint len) { GtkTextLineSegment *seg; g_assert (gtk_text_byte_begins_utf8_char (text)); seg = g_slice_alloc (CSEG_SIZE (len)); seg->type = (GtkTextLineSegmentClass *)>k_text_char_type; seg->next = NULL; seg->byte_count = len; memcpy (seg->body.chars, text, len); seg->body.chars[len] = '\0'; seg->char_count = g_utf8_strlen (seg->body.chars, seg->byte_count); if (GTK_DEBUG_CHECK (TEXT)) char_segment_self_check (seg); return seg; } GtkTextLineSegment* _gtk_char_segment_new_from_two_strings (const char *text1, guint len1, guint chars1, const char *text2, guint len2, guint chars2) { GtkTextLineSegment *seg; g_assert (gtk_text_byte_begins_utf8_char (text1)); g_assert (gtk_text_byte_begins_utf8_char (text2)); seg = g_slice_alloc (CSEG_SIZE (len1+len2)); seg->type = >k_text_char_type; seg->next = NULL; seg->byte_count = len1 + len2; memcpy (seg->body.chars, text1, len1); memcpy (seg->body.chars + len1, text2, len2); seg->body.chars[len1+len2] = '\0'; seg->char_count = chars1 + chars2; if (GTK_DEBUG_CHECK (TEXT)) char_segment_self_check (seg); return seg; } static void _gtk_char_segment_free (GtkTextLineSegment *seg) { if (seg == NULL) return; g_assert (seg->type == >k_text_char_type); g_slice_free1 (CSEG_SIZE (seg->byte_count), seg); } /* *-------------------------------------------------------------- * * char_segment_split_func -- * * This procedure implements splitting for character segments. * * Results: * The return value is a pointer to a chain of two segments * that have the same characters as segPtr except split * among the two segments. * * Side effects: * Storage for segPtr is freed. * *-------------------------------------------------------------- */ static GtkTextLineSegment * char_segment_split_func (GtkTextLineSegment *seg, int index) { GtkTextLineSegment *new1, *new2; g_assert (index < seg->byte_count); if (GTK_DEBUG_CHECK (TEXT)) { char_segment_self_check (seg); } new1 = _gtk_char_segment_new (seg->body.chars, index); new2 = _gtk_char_segment_new (seg->body.chars + index, seg->byte_count - index); g_assert (gtk_text_byte_begins_utf8_char (new1->body.chars)); g_assert (gtk_text_byte_begins_utf8_char (new2->body.chars)); g_assert (new1->byte_count + new2->byte_count == seg->byte_count); g_assert (new1->char_count + new2->char_count == seg->char_count); new1->next = new2; new2->next = seg->next; if (GTK_DEBUG_CHECK (TEXT)) { char_segment_self_check (new1); char_segment_self_check (new2); } _gtk_char_segment_free (seg); return new1; } /* *-------------------------------------------------------------- * * char_segment_cleanup_func -- * * This procedure merges adjacent character segments into * a single character segment, if possible. * * Arguments: * segPtr: Pointer to the first of two adjacent segments to * join. * line: Line containing segments (not used). * * Results: * The return value is a pointer to the first segment in * the (new) list of segments that used to start with segPtr. * * Side effects: * Storage for the segments may be allocated and freed. * *-------------------------------------------------------------- */ /* ARGSUSED */ static GtkTextLineSegment * char_segment_cleanup_func (GtkTextLineSegment *segPtr, GtkTextLine *line) { GtkTextLineSegment *segPtr2, *newPtr; if (GTK_DEBUG_CHECK (TEXT)) char_segment_self_check (segPtr); segPtr2 = segPtr->next; if ((segPtr2 == NULL) || (segPtr2->type != >k_text_char_type)) { return segPtr; } newPtr = _gtk_char_segment_new_from_two_strings (segPtr->body.chars, segPtr->byte_count, segPtr->char_count, segPtr2->body.chars, segPtr2->byte_count, segPtr2->char_count); newPtr->next = segPtr2->next; if (GTK_DEBUG_CHECK (TEXT)) char_segment_self_check (newPtr); _gtk_char_segment_free (segPtr); _gtk_char_segment_free (segPtr2); return newPtr; } /* *-------------------------------------------------------------- * * char_segment_delete_func -- * * This procedure is invoked to delete a character segment. * * Arguments: * segPtr : Segment to delete * line : Line containing segment * treeGone : Non-zero means the entire tree is being * deleted, so everything must get cleaned up. * * Results: * Always returns 0 to indicate that the segment was deleted. * * Side effects: * Storage for the segment is freed. * *-------------------------------------------------------------- */ /* ARGSUSED */ static int char_segment_delete_func (GtkTextLineSegment *segPtr, GtkTextLine *line, int treeGone) { _gtk_char_segment_free (segPtr); return 0; } /* *-------------------------------------------------------------- * * char_segment_check_func -- * * This procedure is invoked to perform consistency checks * on character segments. * * Arguments: * segPtr : Segment to check * line : Line containing segment * * Results: * None. * * Side effects: * If the segment isn’t inconsistent then the procedure * g_errors. * *-------------------------------------------------------------- */ /* ARGSUSED */ static void char_segment_check_func (GtkTextLineSegment *segPtr, GtkTextLine *line) { char_segment_self_check (segPtr); if (segPtr->next != NULL) { if (segPtr->next->type == >k_text_char_type) { g_error ("adjacent character segments weren't merged"); } } } GtkTextLineSegment* _gtk_toggle_segment_new (GtkTextTagInfo *info, gboolean on) { /* gcc-11 issues a diagnostic here because the size allocated for SEG does not cover the entire size of a GtkTextLineSegment and gcc has no way to know that the union will only be used for limited types and the additional space is not needed. */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" GtkTextLineSegment *seg; seg = g_slice_alloc (TSEG_SIZE); seg->type = on ? >k_text_toggle_on_type : >k_text_toggle_off_type; seg->next = NULL; seg->byte_count = 0; seg->char_count = 0; seg->body.toggle.info = info; seg->body.toggle.inNodeCounts = 0; return seg; #pragma GCC diagnostic pop } void _gtk_toggle_segment_free (GtkTextLineSegment *seg) { if (seg == NULL) return; g_assert (seg->type == >k_text_toggle_on_type || seg->type == >k_text_toggle_off_type); g_slice_free1 (TSEG_SIZE, seg); } /* *-------------------------------------------------------------- * * toggle_segment_delete_func -- * * This procedure is invoked to delete toggle segments. * * Arguments: * segPtr : Segment to check * line : Line containing segment * treeGone : Non-zero means the entire tree is being * deleted so everything must get cleaned up * * Results: * Returns 1 to indicate that the segment may not be deleted, * unless the entire B-tree is going away. * * Side effects: * If the tree is going away then the toggle’s memory is * freed; otherwise the toggle counts in GtkTextBTreeNodes above the * segment get updated. * *-------------------------------------------------------------- */ static int toggle_segment_delete_func (GtkTextLineSegment *segPtr, GtkTextLine *line, int treeGone) { if (treeGone) { _gtk_toggle_segment_free (segPtr); return 0; } /* * This toggle is in the middle of a range of characters that's * being deleted. Refuse to die. We'll be moved to the end of * the deleted range and our cleanup procedure will be called * later. Decrement GtkTextBTreeNode toggle counts here, and set a flag * so we'll re-increment them in the cleanup procedure. */ if (segPtr->body.toggle.inNodeCounts) { _gtk_change_node_toggle_count (line->parent, segPtr->body.toggle.info, -1); segPtr->body.toggle.inNodeCounts = 0; } return 1; } /* *-------------------------------------------------------------- * * toggle_segment_cleanup_func -- * * This procedure is called when a toggle is part of a line that's * been modified in some way. It’s invoked after the * modifications are complete. * * Arguments: * segPtr : Segment to check * line : Line that now contains segment * * Results: * The return value is the head segment in a new list * that is to replace the tail of the line that used to * start at segPtr. This allows the procedure to delete * or modify segPtr. * * Side effects: * Toggle counts in the GtkTextBTreeNodes above the new line will be * updated if they’re not already. Toggles may be collapsed * if there are duplicate toggles at the same position. * *-------------------------------------------------------------- */ static GtkTextLineSegment * toggle_segment_cleanup_func (GtkTextLineSegment *segPtr, GtkTextLine *line) { GtkTextLineSegment *segPtr2, *prevPtr; int counts; /* * If this is a toggle-off segment, look ahead through the next * segments to see if there's a toggle-on segment for the same tag * before any segments with non-zero size. If so then the two * toggles cancel each other; remove them both. */ if (segPtr->type == >k_text_toggle_off_type) { for (prevPtr = segPtr, segPtr2 = prevPtr->next; (segPtr2 != NULL) && (segPtr2->byte_count == 0); prevPtr = segPtr2, segPtr2 = prevPtr->next) { if (segPtr2->type != >k_text_toggle_on_type) { continue; } if (segPtr2->body.toggle.info != segPtr->body.toggle.info) { continue; } counts = segPtr->body.toggle.inNodeCounts + segPtr2->body.toggle.inNodeCounts; if (counts != 0) { _gtk_change_node_toggle_count (line->parent, segPtr->body.toggle.info, -counts); } prevPtr->next = segPtr2->next; _gtk_toggle_segment_free (segPtr2); segPtr2 = segPtr->next; _gtk_toggle_segment_free (segPtr); return segPtr2; } } if (!segPtr->body.toggle.inNodeCounts) { _gtk_change_node_toggle_count (line->parent, segPtr->body.toggle.info, 1); segPtr->body.toggle.inNodeCounts = 1; } return segPtr; } /* *-------------------------------------------------------------- * * toggle_segment_line_change_func -- * * This procedure is invoked when a toggle segment is about * to move from one line to another. * * Arguments: * segPtr : Segment to check * line : Line that used to contain segment * * Results: * None. * * Side effects: * Toggle counts are decremented in the GtkTextBTreeNodes above the line. * *-------------------------------------------------------------- */ static void toggle_segment_line_change_func (GtkTextLineSegment *segPtr, GtkTextLine *line) { if (segPtr->body.toggle.inNodeCounts) { _gtk_change_node_toggle_count (line->parent, segPtr->body.toggle.info, -1); segPtr->body.toggle.inNodeCounts = 0; } } /* * Virtual tables */ const GtkTextLineSegmentClass gtk_text_char_type = { "character", /* name */ 0, /* leftGravity */ char_segment_split_func, /* splitFunc */ char_segment_delete_func, /* deleteFunc */ char_segment_cleanup_func, /* cleanupFunc */ NULL, /* lineChangeFunc */ char_segment_check_func /* checkFunc */ }; /* * Type record for segments marking the beginning of a tagged * range: */ const GtkTextLineSegmentClass gtk_text_toggle_on_type = { "toggleOn", /* name */ 0, /* leftGravity */ NULL, /* splitFunc */ toggle_segment_delete_func, /* deleteFunc */ toggle_segment_cleanup_func, /* cleanupFunc */ toggle_segment_line_change_func, /* lineChangeFunc */ _gtk_toggle_segment_check_func /* checkFunc */ }; /* * Type record for segments marking the end of a tagged * range: */ const GtkTextLineSegmentClass gtk_text_toggle_off_type = { "toggleOff", /* name */ 1, /* leftGravity */ NULL, /* splitFunc */ toggle_segment_delete_func, /* deleteFunc */ toggle_segment_cleanup_func, /* cleanupFunc */ toggle_segment_line_change_func, /* lineChangeFunc */ _gtk_toggle_segment_check_func /* checkFunc */ };