/* * 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 "gtktextbtree.h" #include #include #include #include #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 */ };