diff options
Diffstat (limited to 'gtk/gtktextsegment.c')
-rw-r--r-- | gtk/gtktextsegment.c | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/gtk/gtktextsegment.c b/gtk/gtktextsegment.c new file mode 100644 index 000000000..e4effa1b5 --- /dev/null +++ b/gtk/gtktextsegment.c @@ -0,0 +1,626 @@ +/* + * 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 <hp@redhat.com> + * + * 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 "gtktextbtree.h" +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include "gtktexttag.h" +#include "gtktexttagtable.h" +#include "gtktextlayout.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_line(iter); + tree = gtk_text_iter_get_btree(iter); + + count = gtk_text_iter_get_line_byte(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("char_segment_check_func: segment has size <= 0"); + } + + if (strlen(seg->body.chars) != seg->byte_count) + { + g_error("char_segment_check_func: segment has wrong size"); + } + + if (gtk_text_view_num_utf_chars(seg->body.chars, seg->byte_count) != seg->char_count) + { + g_error("char segment has wrong character count"); + } +} + +GtkTextLineSegment* +char_segment_new(const gchar *text, guint len) +{ + GtkTextLineSegment *seg; + + g_assert(gtk_text_byte_begins_utf8_char(text)); + + seg = g_malloc(CSEG_SIZE(len)); + seg->type = >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 = gtk_text_view_num_utf_chars(seg->body.chars, seg->byte_count); + + if (gtk_debug_flags & GTK_DEBUG_TEXT) + char_segment_self_check(seg); + + return seg; +} + +GtkTextLineSegment* +char_segment_new_from_two_strings(const gchar *text1, guint len1, + const gchar *text2, guint len2) +{ + GtkTextLineSegment *seg; + + g_assert(gtk_text_byte_begins_utf8_char(text1)); + g_assert(gtk_text_byte_begins_utf8_char(text2)); + + seg = g_malloc(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'; + + /* In principle this function could probably take chars1 and chars2 + as args, since it's typically used to merge two char segments */ + seg->char_count = gtk_text_view_num_utf_chars(seg->body.chars, seg->byte_count); + + if (gtk_debug_flags & GTK_DEBUG_TEXT) + char_segment_self_check(seg); + + return 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_flags & GTK_DEBUG_TEXT) + { + char_segment_self_check(seg); + } + + new1 = char_segment_new(seg->body.chars, index); + new2 = 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_flags & GTK_DEBUG_TEXT) + { + char_segment_self_check(new1); + char_segment_self_check(new2); + } + + g_free(seg); + return new1; +} + +/* + *-------------------------------------------------------------- + * + * char_segment_cleanup_func -- + * + * This procedure merges adjacent character segments into + * a single character segment, if possible. + * + * 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(segPtr, line) + GtkTextLineSegment *segPtr; /* Pointer to first of two adjacent + * segments to join. */ + GtkTextLine *line; /* Line containing segments (not + * used). */ +{ + GtkTextLineSegment *segPtr2, *newPtr; + + if (gtk_debug_flags & GTK_DEBUG_TEXT) + char_segment_self_check(segPtr); + + segPtr2 = segPtr->next; + if ((segPtr2 == NULL) || (segPtr2->type != >k_text_char_type)) + { + return segPtr; + } + + newPtr = char_segment_new_from_two_strings(segPtr->body.chars, segPtr->byte_count, + segPtr2->body.chars, segPtr2->byte_count); + + newPtr->next = segPtr2->next; + + if (gtk_debug_flags & GTK_DEBUG_TEXT) + char_segment_self_check(newPtr); + + g_free(segPtr); + g_free(segPtr2); + return newPtr; +} + +/* + *-------------------------------------------------------------- + * + * char_segment_delete_func -- + * + * This procedure is invoked to delete a character segment. + * + * 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(segPtr, line, treeGone) + GtkTextLineSegment *segPtr; /* Segment to delete. */ + GtkTextLine *line; /* Line containing segment. */ + int treeGone; /* Non-zero means the entire tree is + * being deleted, so everything must + * get cleaned up. */ +{ + g_free((char*) segPtr); + return 0; +} + +/* + *-------------------------------------------------------------- + * + * char_segment_check_func -- + * + * This procedure is invoked to perform consistency checks + * on character segments. + * + * Results: + * None. + * + * Side effects: + * If the segment isn't inconsistent then the procedure + * g_errors. + * + *-------------------------------------------------------------- + */ + + /* ARGSUSED */ +static void +char_segment_check_func(segPtr, line) + GtkTextLineSegment *segPtr; /* Segment to check. */ + GtkTextLine *line; /* Line containing segment. */ +{ + char_segment_self_check(segPtr); + + if (segPtr->next == NULL) + { + if (segPtr->body.chars[segPtr->byte_count-1] != '\n') + { + g_error("char_segment_check_func: line doesn't end with newline"); + } + } + else + { + if (segPtr->next->type == >k_text_char_type) + { + g_error("char_segment_check_func: adjacent character segments weren't merged"); + } + } +} + +GtkTextLineSegment* +toggle_segment_new(GtkTextTagInfo *info, gboolean on) +{ + GtkTextLineSegment *seg; + + seg = g_malloc(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; +} + +/* + *-------------------------------------------------------------- + * + * toggle_segment_delete_func -- + * + * This procedure is invoked to delete toggle segments. + * + * 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(segPtr, line, treeGone) + GtkTextLineSegment *segPtr; /* Segment to check. */ + GtkTextLine *line; /* Line containing segment. */ + int treeGone; /* Non-zero means the entire tree is + * being deleted, so everything must + * get cleaned up. */ +{ + if (treeGone) + { + g_free((char *) 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) + { + 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. + * + * 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(segPtr, line) + GtkTextLineSegment *segPtr; /* Segment to check. */ + GtkTextLine *line; /* Line that now contains segment. */ +{ + 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) + { + change_node_toggle_count(line->parent, + segPtr->body.toggle.info, -counts); + } + prevPtr->next = segPtr2->next; + g_free((char *) segPtr2); + segPtr2 = segPtr->next; + g_free((char *) segPtr); + return segPtr2; + } + } + + if (!segPtr->body.toggle.inNodeCounts) + { + 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. + * + * Results: + * None. + * + * Side effects: + * Toggle counts are decremented in the GtkTextBTreeNodes above the line. + * + *-------------------------------------------------------------- + */ + +static void +toggle_segment_line_change_func(segPtr, line) + GtkTextLineSegment *segPtr; /* Segment to check. */ + GtkTextLine *line; /* Line that used to contain segment. */ +{ + if (segPtr->body.toggle.inNodeCounts) + { + change_node_toggle_count(line->parent, + segPtr->body.toggle.info, -1); + segPtr->body.toggle.inNodeCounts = 0; + } +} + +/* + * Virtual tables + */ + + +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: + */ + +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 */ + toggle_segment_check_func /* checkFunc */ +}; + +/* + * Type record for segments marking the end of a tagged + * range: + */ + +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 */ + toggle_segment_check_func /* checkFunc */ +}; |