summaryrefslogtreecommitdiff
path: root/gtk/gtktextsegment.c
diff options
context:
space:
mode:
Diffstat (limited to 'gtk/gtktextsegment.c')
-rw-r--r--gtk/gtktextsegment.c626
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 = &gtk_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 = &gtk_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 != &gtk_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 == &gtk_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 ? &gtk_text_toggle_on_type : &gtk_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 == &gtk_text_toggle_off_type)
+ {
+ for (prevPtr = segPtr, segPtr2 = prevPtr->next;
+ (segPtr2 != NULL) && (segPtr2->byte_count == 0);
+ prevPtr = segPtr2, segPtr2 = prevPtr->next)
+ {
+ if (segPtr2->type != &gtk_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 */
+};