/* * tkTextBTree.c (CTk) -- * * This file contains code that manages the B-tree representation * of text for Tk's text widget and implements character and * toggle segment types. * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * @(#) $Id: ctk.shar,v 1.50 1996/01/15 14:47:16 andrewm Exp andrewm $ */ #include "tkInt.h" #include "tkPort.h" #include "tkText.h" /* * The data structure below keeps summary information about one tag as part * of the tag information in a node. */ typedef struct Summary { TkTextTag *tagPtr; /* Handle for tag. */ int toggleCount; /* Number of transitions into or * out of this tag that occur in * the subtree rooted at this node. */ struct Summary *nextPtr; /* Next in list of all tags for same * node, or NULL if at end of list. */ } Summary; /* * The data structure below defines a node in the B-tree. */ typedef struct Node { struct Node *parentPtr; /* Pointer to parent node, or NULL if * this is the root. */ struct Node *nextPtr; /* Next in list of siblings with the * same parent node, or NULL for end * of list. */ Summary *summaryPtr; /* First in malloc-ed list of info * about tags in this subtree (NULL if * no tag info in the subtree). */ int level; /* Level of this node in the B-tree. * 0 refers to the bottom of the tree * (children are lines, not nodes). */ union { /* First in linked list of children. */ struct Node *nodePtr; /* Used if level > 0. */ TkTextLine *linePtr; /* Used if level == 0. */ } children; int numChildren; /* Number of children of this node. */ int numLines; /* Total number of lines (leaves) in * the subtree rooted here. */ } Node; /* * Upper and lower bounds on how many children a node may have: * rebalance when either of these limits is exceeded. MAX_CHILDREN * should be twice MIN_CHILDREN and MIN_CHILDREN must be >= 2. */ #define MAX_CHILDREN 12 #define MIN_CHILDREN 6 /* * The data structure below defines an entire B-tree. */ typedef struct BTree { Node *rootPtr; /* Pointer to root of B-tree. */ } BTree; /* * The structure below is used to pass information between * TkBTreeGetTags and IncCount: */ typedef struct TagInfo { int numTags; /* Number of tags for which there * is currently information in * tags and counts. */ int arraySize; /* Number of entries allocated for * tags and counts. */ TkTextTag **tagPtrs; /* Array of tags seen so far. * Malloc-ed. */ int *counts; /* Toggle count (so far) for each * entry in tags. Malloc-ed. */ } TagInfo; /* * Variable that indicates whether to enable consistency checks for * debugging. */ int tkBTreeDebug = 0; /* * Macros that determine how much space to allocate for new segments: */ #define CSEG_SIZE(chars) ((unsigned) (Tk_Offset(TkTextSegment, body) \ + 1 + (chars))) #define TSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \ + sizeof(TkTextToggle))) /* * Forward declarations for procedures defined in this file: */ static void ChangeNodeToggleCount _ANSI_ARGS_((Node *nodePtr, TkTextTag *tagPtr, int delta)); static void CharCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static int CharDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr, int treeGone)); static TkTextSegment * CharCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static TkTextSegment * CharSplitProc _ANSI_ARGS_((TkTextSegment *segPtr, int index)); static void CheckNodeConsistency _ANSI_ARGS_((Node *nodePtr)); static void CleanupLine _ANSI_ARGS_((TkTextLine *linePtr)); static void DeleteSummaries _ANSI_ARGS_((Summary *tagPtr)); static void DestroyNode _ANSI_ARGS_((Node *nodePtr)); static void IncCount _ANSI_ARGS_((TkTextTag *tagPtr, int inc, TagInfo *tagInfoPtr)); static void Rebalance _ANSI_ARGS_((BTree *treePtr, Node *nodePtr)); static void RecomputeNodeCounts _ANSI_ARGS_((Node *nodePtr)); static TkTextSegment * SplitSeg _ANSI_ARGS_((TkTextIndex *indexPtr)); static void ToggleCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static TkTextSegment * ToggleCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static int ToggleDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr, int treeGone)); static void ToggleLineChangeProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); /* * Type record for character segments: */ Tk_SegType tkTextCharType = { "character", /* name */ 0, /* leftGravity */ CharSplitProc, /* splitProc */ CharDeleteProc, /* deleteProc */ CharCleanupProc, /* cleanupProc */ (Tk_SegLineChangeProc *) NULL, /* lineChangeProc */ TkTextCharLayoutProc, /* layoutProc */ CharCheckProc /* checkProc */ }; /* * Type record for segments marking the beginning of a tagged * range: */ Tk_SegType tkTextToggleOnType = { "toggleOn", /* name */ 0, /* leftGravity */ (Tk_SegSplitProc *) NULL, /* splitProc */ ToggleDeleteProc, /* deleteProc */ ToggleCleanupProc, /* cleanupProc */ ToggleLineChangeProc, /* lineChangeProc */ (Tk_SegLayoutProc *) NULL, /* layoutProc */ ToggleCheckProc /* checkProc */ }; /* * Type record for segments marking the end of a tagged * range: */ Tk_SegType tkTextToggleOffType = { "toggleOff", /* name */ 1, /* leftGravity */ (Tk_SegSplitProc *) NULL, /* splitProc */ ToggleDeleteProc, /* deleteProc */ ToggleCleanupProc, /* cleanupProc */ ToggleLineChangeProc, /* lineChangeProc */ (Tk_SegLayoutProc *) NULL, /* layoutProc */ ToggleCheckProc /* checkProc */ }; /* *---------------------------------------------------------------------- * * TkBTreeCreate -- * * This procedure is called to create a new text B-tree. * * Results: * The return value is a pointer to a new B-tree containing * one line with nothing but a newline character. * * Side effects: * Memory is allocated and initialized. * *---------------------------------------------------------------------- */ TkTextBTree TkBTreeCreate() { register BTree *treePtr; register Node *rootPtr; register TkTextLine *linePtr, *linePtr2; register TkTextSegment *segPtr; /* * The tree will initially have two empty lines. The second line * isn't actually part of the tree's contents, but its presence * makes several operations easier. The tree will have one node, * which is also the root of the tree. */ rootPtr = (Node *) ckalloc(sizeof(Node)); linePtr = (TkTextLine *) ckalloc(sizeof(TkTextLine)); linePtr2 = (TkTextLine *) ckalloc(sizeof(TkTextLine)); rootPtr->parentPtr = NULL; rootPtr->nextPtr = NULL; rootPtr->summaryPtr = NULL; rootPtr->level = 0; rootPtr->children.linePtr = linePtr; rootPtr->numChildren = 2; rootPtr->numLines = 2; linePtr->parentPtr = rootPtr; linePtr->nextPtr = linePtr2; segPtr = (TkTextSegment *) ckalloc(CSEG_SIZE(1)); linePtr->segPtr = segPtr; segPtr->typePtr = &tkTextCharType; segPtr->nextPtr = NULL; segPtr->size = 1; segPtr->body.chars[0] = '\n'; segPtr->body.chars[1] = 0; linePtr2->parentPtr = rootPtr; linePtr2->nextPtr = NULL; segPtr = (TkTextSegment *) ckalloc(CSEG_SIZE(1)); linePtr2->segPtr = segPtr; segPtr->typePtr = &tkTextCharType; segPtr->nextPtr = NULL; segPtr->size = 1; segPtr->body.chars[0] = '\n'; segPtr->body.chars[1] = 0; treePtr = (BTree *) ckalloc(sizeof(BTree)); treePtr->rootPtr = rootPtr; return (TkTextBTree) treePtr; } /* *---------------------------------------------------------------------- * * TkBTreeDestroy -- * * Delete a B-tree, recycling all of the storage it contains. * * Results: * The tree given by treePtr is deleted. TreePtr should never * again be used. * * Side effects: * Memory is freed. * *---------------------------------------------------------------------- */ void TkBTreeDestroy(tree) TkTextBTree tree; /* Pointer to tree to delete. */ { BTree *treePtr = (BTree *) tree; DestroyNode(treePtr->rootPtr); ckfree((char *) treePtr); } /* *---------------------------------------------------------------------- * * DestroyNode -- * * This is a recursive utility procedure used during the deletion * of a B-tree. * * Results: * None. * * Side effects: * All the storage for nodePtr and its descendants is freed. * *---------------------------------------------------------------------- */ static void DestroyNode(nodePtr) register Node *nodePtr; { if (nodePtr->level == 0) { TkTextLine *linePtr; TkTextSegment *segPtr; while (nodePtr->children.linePtr != NULL) { linePtr = nodePtr->children.linePtr; nodePtr->children.linePtr = linePtr->nextPtr; while (linePtr->segPtr != NULL) { segPtr = linePtr->segPtr; linePtr->segPtr = segPtr->nextPtr; (*segPtr->typePtr->deleteProc)(segPtr, linePtr, 1); } ckfree((char *) linePtr); } } else { register Node *childPtr; while (nodePtr->children.nodePtr != NULL) { childPtr = nodePtr->children.nodePtr; nodePtr->children.nodePtr = childPtr->nextPtr; DestroyNode(childPtr); } } DeleteSummaries(nodePtr->summaryPtr); ckfree((char *) nodePtr); } /* *---------------------------------------------------------------------- * * DeleteSummaries -- * * Free up all of the memory in a list of tag summaries associated * with a node. * * Results: * None. * * Side effects: * Storage is released. * *---------------------------------------------------------------------- */ static void DeleteSummaries(summaryPtr) register Summary *summaryPtr; /* First in list of node's tag * summaries. */ { register Summary *nextPtr; while (summaryPtr != NULL) { nextPtr = summaryPtr->nextPtr; ckfree((char *) summaryPtr); summaryPtr = nextPtr; } } /* *---------------------------------------------------------------------- * * TkBTreeInsertChars -- * * Insert characters at a given position in a B-tree. * * Results: * None. * * Side effects: * Characters are added to the B-tree at the given position. * If the string contains newlines, new lines will be added, * which could cause the structure of the B-tree to change. * *---------------------------------------------------------------------- */ void TkBTreeInsertChars(indexPtr, string) register TkTextIndex *indexPtr; /* Indicates where to insert text. * When the procedure returns, this * index is no longer valid because * of changes to the segment * structure. */ char *string; /* Pointer to bytes to insert (may * contain newlines, must be null- * terminated). */ { register Node *nodePtr; register TkTextSegment *prevPtr; /* The segment just before the first * new segment (NULL means new segment * is at beginning of line). */ TkTextSegment *curPtr; /* Current segment; new characters * are inserted just after this one. * NULL means insert at beginning of * line. */ TkTextLine *linePtr; /* Current line (new segments are * added to this line). */ register TkTextSegment *segPtr; TkTextLine *newLinePtr; int chunkSize; /* # characters in current chunk. */ register char *eol; /* Pointer to character just after last * one in current chunk. */ int changeToLineCount; /* Counts change to total number of * lines in file. */ prevPtr = SplitSeg(indexPtr); linePtr = indexPtr->linePtr; curPtr = prevPtr; /* * Chop the string up into lines and create a new segment for * each line, plus a new line for the leftovers from the * previous line. */ changeToLineCount = 0; while (*string != 0) { for (eol = string; *eol != 0; eol++) { if (*eol == '\n') { eol++; break; } } chunkSize = eol-string; segPtr = (TkTextSegment *) ckalloc(CSEG_SIZE(chunkSize)); segPtr->typePtr = &tkTextCharType; if (curPtr == NULL) { segPtr->nextPtr = linePtr->segPtr; linePtr->segPtr = segPtr; } else { segPtr->nextPtr = curPtr->nextPtr; curPtr->nextPtr = segPtr; } segPtr->size = chunkSize; strncpy(segPtr->body.chars, string, (size_t) chunkSize); segPtr->body.chars[chunkSize] = 0; curPtr = segPtr; if (eol[-1] != '\n') { break; } /* * The chunk ended with a newline, so create a new TkTextLine * and move the remainder of the old line to it. */ newLinePtr = (TkTextLine *) ckalloc(sizeof(TkTextLine)); newLinePtr->parentPtr = linePtr->parentPtr; newLinePtr->nextPtr = linePtr->nextPtr; linePtr->nextPtr = newLinePtr; newLinePtr->segPtr = segPtr->nextPtr; segPtr->nextPtr = NULL; linePtr = newLinePtr; curPtr = NULL; changeToLineCount++; string = eol; } /* * Cleanup the starting line for the insertion, plus the ending * line if it's different. */ CleanupLine(indexPtr->linePtr); if (linePtr != indexPtr->linePtr) { CleanupLine(linePtr); } /* * Increment the line counts in all the parent nodes of the insertion * point, then rebalance the tree if necessary. */ for (nodePtr = linePtr->parentPtr ; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines += changeToLineCount; } nodePtr = linePtr->parentPtr; nodePtr->numChildren += changeToLineCount; if (nodePtr->numChildren > MAX_CHILDREN) { Rebalance((BTree *) indexPtr->tree, nodePtr); } if (tkBTreeDebug) { TkBTreeCheck(indexPtr->tree); } } /* *-------------------------------------------------------------- * * SplitSeg -- * * This procedure is called before adding or deleting * segments. It does three things: (a) it finds the segment * containing indexPtr; (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 indexPtr (as * described above). If the segment corresponding to * indexPtr is the first in its line then the return * value is NULL. * * Side effects: * The segment referred to by indexPtr is split unless * indexPtr refers to its first character. * *-------------------------------------------------------------- */ static TkTextSegment * SplitSeg(indexPtr) TkTextIndex *indexPtr; /* Index identifying position * at which to split a segment. */ { TkTextSegment *prevPtr, *segPtr; int count; for (count = indexPtr->charIndex, prevPtr = NULL, segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; count -= segPtr->size, prevPtr = segPtr, segPtr = segPtr->nextPtr) { if (segPtr->size > count) { if (count == 0) { return prevPtr; } segPtr = (*segPtr->typePtr->splitProc)(segPtr, count); if (prevPtr == NULL) { indexPtr->linePtr->segPtr = segPtr; } else { prevPtr->nextPtr = segPtr; } return segPtr; } else if ((segPtr->size == 0) && (count == 0) && !segPtr->typePtr->leftGravity) { return prevPtr; } } panic("SplitSeg reached end of line!"); return NULL; } /* *-------------------------------------------------------------- * * CleanupLine -- * * This procedure is called after modifications have been * made to a line. It scans over all of the segments in * the line, giving each a chance to clean itself up, e.g. * by merging with the following segments, updating internal * information, etc. * * Results: * None. * * Side effects: * Depends on what the segment-specific cleanup procedures do. * *-------------------------------------------------------------- */ static void CleanupLine(linePtr) TkTextLine *linePtr; /* Line to be cleaned up. */ { TkTextSegment *segPtr, **prevPtrPtr; int anyChanges; /* * Make a pass over all of the segments in the line, giving each * a chance to clean itself up. This could potentially change * the structure of the line, e.g. by merging two segments * together or having two segments cancel themselves; if so, * then repeat the whole process again, since the first structure * change might make other structure changes possible. Repeat * until eventually there are no changes. */ while (1) { anyChanges = 0; for (prevPtrPtr = &linePtr->segPtr, segPtr = *prevPtrPtr; segPtr != NULL; prevPtrPtr = &(*prevPtrPtr)->nextPtr, segPtr = *prevPtrPtr) { if (segPtr->typePtr->cleanupProc != NULL) { *prevPtrPtr = (*segPtr->typePtr->cleanupProc)(segPtr, linePtr); if (segPtr != *prevPtrPtr) { anyChanges = 1; } } } if (!anyChanges) { break; } } } /* *---------------------------------------------------------------------- * * TkBTreeDeleteChars -- * * Delete a range of characters from a B-tree. The caller * must make sure that the final newline of the B-tree is * never deleted. * * Results: * None. * * Side effects: * Information is deleted from the B-tree. This can cause the * internal structure of the B-tree to change. Note: because * of changes to the B-tree structure, the indices pointed * to by index1Ptr and index2Ptr should not be used after this * procedure returns. * *---------------------------------------------------------------------- */ void TkBTreeDeleteChars(index1Ptr, index2Ptr) register TkTextIndex *index1Ptr; /* Indicates first character that is * to be deleted. */ register TkTextIndex *index2Ptr; /* Indicates character just after the * last one that is to be deleted. */ { TkTextSegment *prevPtr; /* The segment just before the start * of the deletion range. */ TkTextSegment *lastPtr; /* The segment just after the end * of the deletion range. */ TkTextSegment *segPtr, *nextPtr; TkTextLine *curLinePtr; Node *curNodePtr, *nodePtr; /* * Tricky point: split at index2Ptr first; otherwise the split * at index2Ptr may invalidate segPtr and/or prevPtr. */ lastPtr = SplitSeg(index2Ptr); if (lastPtr != NULL) { lastPtr = lastPtr->nextPtr; } else { lastPtr = index2Ptr->linePtr->segPtr; } prevPtr = SplitSeg(index1Ptr); if (prevPtr != NULL) { segPtr = prevPtr->nextPtr; prevPtr->nextPtr = lastPtr; } else { segPtr = index1Ptr->linePtr->segPtr; index1Ptr->linePtr->segPtr = lastPtr; } /* * Delete all of the segments between prevPtr and lastPtr. */ curLinePtr = index1Ptr->linePtr; curNodePtr = curLinePtr->parentPtr; while (segPtr != lastPtr) { if (segPtr == NULL) { TkTextLine *nextLinePtr; /* * We just ran off the end of a line. First find the * next line, then go back to the old line and delete it * (unless it's the starting line for the range). */ nextLinePtr = TkBTreeNextLine(curLinePtr); if (curLinePtr != index1Ptr->linePtr) { if (curNodePtr == index1Ptr->linePtr->parentPtr) { index1Ptr->linePtr->nextPtr = curLinePtr->nextPtr; } else { curNodePtr->children.linePtr = curLinePtr->nextPtr; } for (nodePtr = curNodePtr; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines--; } curNodePtr->numChildren--; ckfree((char *) curLinePtr); } curLinePtr = nextLinePtr; segPtr = curLinePtr->segPtr; /* * If the node is empty then delete it and its parents, * recursively upwards until a non-empty node is found. */ while (curNodePtr->numChildren == 0) { Node *parentPtr; parentPtr = curNodePtr->parentPtr; if (parentPtr->children.nodePtr == curNodePtr) { parentPtr->children.nodePtr = curNodePtr->nextPtr; } else { Node *prevNodePtr = parentPtr->children.nodePtr; while (prevNodePtr->nextPtr != curNodePtr) { prevNodePtr = prevNodePtr->nextPtr; } prevNodePtr->nextPtr = curNodePtr->nextPtr; } parentPtr->numChildren--; ckfree((char *) curNodePtr); curNodePtr = parentPtr; } curNodePtr = curLinePtr->parentPtr; continue; } nextPtr = segPtr->nextPtr; if ((*segPtr->typePtr->deleteProc)(segPtr, curLinePtr, 0) != 0) { /* * This segment refuses to die. Move it to prevPtr and * advance prevPtr if the segment has left gravity. */ if (prevPtr == NULL) { segPtr->nextPtr = index1Ptr->linePtr->segPtr; index1Ptr->linePtr->segPtr = segPtr; } else { segPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = segPtr; } if (segPtr->typePtr->leftGravity) { prevPtr = segPtr; } } segPtr = nextPtr; } /* * If the beginning and end of the deletion range are in different * lines, join the two lines together and discard the ending line. */ if (index1Ptr->linePtr != index2Ptr->linePtr) { TkTextLine *prevLinePtr; for (segPtr = lastPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { if (segPtr->typePtr->lineChangeProc != NULL) { (*segPtr->typePtr->lineChangeProc)(segPtr, index2Ptr->linePtr); } } curNodePtr = index2Ptr->linePtr->parentPtr; for (nodePtr = curNodePtr; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines--; } curNodePtr->numChildren--; prevLinePtr = curNodePtr->children.linePtr; if (prevLinePtr == index2Ptr->linePtr) { curNodePtr->children.linePtr = index2Ptr->linePtr->nextPtr; } else { while (prevLinePtr->nextPtr != index2Ptr->linePtr) { prevLinePtr = prevLinePtr->nextPtr; } prevLinePtr->nextPtr = index2Ptr->linePtr->nextPtr; } ckfree((char *) index2Ptr->linePtr); Rebalance((BTree *) index2Ptr->tree, curNodePtr); } /* * Cleanup the segments in the new line. */ CleanupLine(index1Ptr->linePtr); /* * Lastly, rebalance the first node of the range. */ Rebalance((BTree *) index1Ptr->tree, index1Ptr->linePtr->parentPtr); if (tkBTreeDebug) { TkBTreeCheck(index1Ptr->tree); } } /* *---------------------------------------------------------------------- * * TkBTreeFindLine -- * * Find a particular line in a B-tree based on its line number. * * Results: * The return value is a pointer to the line structure for the * line whose index is "line", or NULL if no such line exists. * * Side effects: * None. * *---------------------------------------------------------------------- */ TkTextLine * TkBTreeFindLine(tree, line) TkTextBTree tree; /* B-tree in which to find line. */ int line; /* Index of desired line. */ { BTree *treePtr = (BTree *) tree; register Node *nodePtr; register TkTextLine *linePtr; int linesLeft; nodePtr = treePtr->rootPtr; linesLeft = line; if ((line < 0) || (line >= nodePtr->numLines)) { return NULL; } /* * Work down through levels of the tree until a node is found at * level 0. */ while (nodePtr->level != 0) { for (nodePtr = nodePtr->children.nodePtr; nodePtr->numLines <= linesLeft; nodePtr = nodePtr->nextPtr) { if (nodePtr == NULL) { panic("TkBTreeFindLine ran out of nodes"); } linesLeft -= nodePtr->numLines; } } /* * Work through the lines attached to the level-0 node. */ for (linePtr = nodePtr->children.linePtr; linesLeft > 0; linePtr = linePtr->nextPtr) { if (linePtr == NULL) { panic("TkBTreeFindLine ran out of lines"); } linesLeft -= 1; } return linePtr; } /* *---------------------------------------------------------------------- * * TkBTreeNextLine -- * * Given an existing line in a B-tree, this procedure locates the * next line in the B-tree. This procedure is used for scanning * through the B-tree. * * Results: * The return value is a pointer to the line that immediately * follows linePtr, or NULL if there is no such line. * * Side effects: * None. * *---------------------------------------------------------------------- */ TkTextLine * TkBTreeNextLine(linePtr) register TkTextLine *linePtr; /* Pointer to existing line in * B-tree. */ { register Node *nodePtr; if (linePtr->nextPtr != NULL) { return linePtr->nextPtr; } /* * This was the last line associated with the particular parent node. * Search up the tree for the next node, then search down from that * node to find the first line, */ for (nodePtr = linePtr->parentPtr; ; nodePtr = nodePtr->parentPtr) { if (nodePtr->nextPtr != NULL) { nodePtr = nodePtr->nextPtr; break; } if (nodePtr->parentPtr == NULL) { return (TkTextLine *) NULL; } } while (nodePtr->level > 0) { nodePtr = nodePtr->children.nodePtr; } return nodePtr->children.linePtr; } /* *---------------------------------------------------------------------- * * TkBTreeLineIndex -- * * Given a pointer to a line in a B-tree, return the numerical * index of that line. * * Results: * The result is the index of linePtr within the tree, where 0 * corresponds to the first line in the tree. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkBTreeLineIndex(linePtr) TkTextLine *linePtr; /* Pointer to existing line in * B-tree. */ { register TkTextLine *linePtr2; register Node *nodePtr, *parentPtr, *nodePtr2; int index; /* * First count how many lines precede this one in its level-0 * node. */ nodePtr = linePtr->parentPtr; index = 0; for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr; linePtr2 = linePtr2->nextPtr) { if (linePtr2 == NULL) { panic("TkBTreeLineIndex couldn't find line"); } index += 1; } /* * Now work up through the levels of the tree one at a time, * counting how many lines are in nodes preceding the current * node. */ for (parentPtr = nodePtr->parentPtr ; parentPtr != NULL; nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) { for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr; nodePtr2 = nodePtr2->nextPtr) { if (nodePtr2 == NULL) { panic("TkBTreeLineIndex couldn't find node"); } index += nodePtr2->numLines; } } return index; } /* *---------------------------------------------------------------------- * * TkBTreeLinkSegment -- * * This procedure adds a new segment to a B-tree at a given * location. * * Results: * None. * * Side effects: * SegPtr will be linked into its tree. * *---------------------------------------------------------------------- */ /* ARGSUSED */ void TkBTreeLinkSegment(segPtr, indexPtr) TkTextSegment *segPtr; /* Pointer to new segment to be added to * B-tree. Should be completely initialized * by caller except for nextPtr field. */ TkTextIndex *indexPtr; /* Where to add segment: it gets linked * in just before the segment indicated * here. */ { register TkTextSegment *prevPtr; prevPtr = SplitSeg(indexPtr); if (prevPtr == NULL) { segPtr->nextPtr = indexPtr->linePtr->segPtr; indexPtr->linePtr->segPtr = segPtr; } else { segPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = segPtr; } CleanupLine(indexPtr->linePtr); if (tkBTreeDebug) { TkBTreeCheck(indexPtr->tree); } } /* *---------------------------------------------------------------------- * * TkBTreeUnlinkSegment -- * * This procedure unlinks a segment from its line in a B-tree. * * Results: * None. * * Side effects: * SegPtr will be unlinked from linePtr. The segment itself * isn't modified by this procedure. * *---------------------------------------------------------------------- */ /* ARGSUSED */ void TkBTreeUnlinkSegment(tree, segPtr, linePtr) TkTextBTree tree; /* Tree containing segment. */ TkTextSegment *segPtr; /* Segment to be unlinked. */ TkTextLine *linePtr; /* Line that currently contains * segment. */ { register TkTextSegment *prevPtr; if (linePtr->segPtr == segPtr) { linePtr->segPtr = segPtr->nextPtr; } else { for (prevPtr = linePtr->segPtr; prevPtr->nextPtr != segPtr; prevPtr = prevPtr->nextPtr) { /* Empty loop body. */ } prevPtr->nextPtr = segPtr->nextPtr; } CleanupLine(linePtr); } /* *---------------------------------------------------------------------- * * TkBTreeTag -- * * Turn a given tag on or off for a given range of characters in * a B-tree of text. * * Results: * None. * * Side effects: * The given tag is added to the given range of characters * in the tree or removed from all those characters, depending * on the "add" argument. The structure of the btree is modified * enough that index1Ptr and index2Ptr are no longer valid after * this procedure returns, and the indexes may be modified by * this procedure. * *---------------------------------------------------------------------- */ void TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) register TkTextIndex *index1Ptr; /* Indicates first character in * range. */ register TkTextIndex *index2Ptr; /* Indicates character just after the * last one in range. */ TkTextTag *tagPtr; /* Tag to add or remove. */ int add; /* One means add tag to the given * range of characters; zero means * remove the tag from the range. */ { TkTextSegment *segPtr, *prevPtr; TkTextSearch search; TkTextLine *cleanupLinePtr; int oldState; /* * See whether the tag is present at the start of the range. If * the state doesn't already match what we want then add a toggle * there. */ oldState = TkBTreeCharTagged(index1Ptr, tagPtr); if ((add != 0) ^ oldState) { segPtr = (TkTextSegment *) ckalloc(TSEG_SIZE); segPtr->typePtr = (add) ? &tkTextToggleOnType : &tkTextToggleOffType; prevPtr = SplitSeg(index1Ptr); if (prevPtr == NULL) { segPtr->nextPtr = index1Ptr->linePtr->segPtr; index1Ptr->linePtr->segPtr = segPtr; } else { segPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = segPtr; } segPtr->size = 0; segPtr->body.toggle.tagPtr = tagPtr; segPtr->body.toggle.inNodeCounts = 0; } /* * Scan the range of characters and delete any internal tag * transitions. Keep track of what the old state was at the end * of the range, and add a toggle there if it's needed. */ TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search); cleanupLinePtr = index1Ptr->linePtr; while (TkBTreeNextTag(&search)) { oldState ^= 1; segPtr = search.segPtr; prevPtr = search.curIndex.linePtr->segPtr; if (prevPtr == segPtr) { search.curIndex.linePtr->segPtr = segPtr->nextPtr; } else { while (prevPtr->nextPtr != segPtr) { prevPtr = prevPtr->nextPtr; } prevPtr->nextPtr = segPtr->nextPtr; } if (segPtr->body.toggle.inNodeCounts) { ChangeNodeToggleCount(search.curIndex.linePtr->parentPtr, segPtr->body.toggle.tagPtr, -1); segPtr->body.toggle.inNodeCounts = 0; } ckfree((char *) segPtr); /* * The code below is a bit tricky. After deleting a toggle * we eventually have to call CleanupLine, in order to allow * character segments to be merged together. To do this, we * remember in cleanupLinePtr a line that needs to be * cleaned up, but we don't clean it up until we've moved * on to a different line. That way the cleanup process * won't goof up segPtr. */ if (cleanupLinePtr != search.curIndex.linePtr) { CleanupLine(cleanupLinePtr); cleanupLinePtr = search.curIndex.linePtr; } } if ((add != 0) ^ oldState) { segPtr = (TkTextSegment *) ckalloc(TSEG_SIZE); segPtr->typePtr = (add) ? &tkTextToggleOffType : &tkTextToggleOnType; prevPtr = SplitSeg(index2Ptr); if (prevPtr == NULL) { segPtr->nextPtr = index2Ptr->linePtr->segPtr; index2Ptr->linePtr->segPtr = segPtr; } else { segPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = segPtr; } segPtr->size = 0; segPtr->body.toggle.tagPtr = tagPtr; segPtr->body.toggle.inNodeCounts = 0; } /* * Cleanup cleanupLinePtr and the last line of the range, if * these are different. */ CleanupLine(cleanupLinePtr); if (cleanupLinePtr != index2Ptr->linePtr) { CleanupLine(index2Ptr->linePtr); } if (tkBTreeDebug) { TkBTreeCheck(index1Ptr->tree); } } /* *---------------------------------------------------------------------- * * ChangeNodeToggleCount -- * * This procedure increments or decrements the toggle count for * a particular tag in a particular node and all its ancestors. * * Results: * None. * * Side effects: * The toggle count for tag is adjusted up or down by "delta" in * nodePtr. * *---------------------------------------------------------------------- */ static void ChangeNodeToggleCount(nodePtr, tagPtr, delta) register Node *nodePtr; /* Node whose toggle count for a tag * must be changed. */ TkTextTag *tagPtr; /* Information about tag. */ int delta; /* Amount to add to current toggle * count for tag (may be negative). */ { register Summary *summaryPtr, *prevPtr; /* * Iterate over the node and all of its ancestors. */ for ( ; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { /* * See if there's already an entry for this tag for this node. If so, * perhaps all we have to do is adjust its count. */ for (prevPtr = NULL, summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; prevPtr = summaryPtr, summaryPtr = summaryPtr->nextPtr) { if (summaryPtr->tagPtr != tagPtr) { continue; } summaryPtr->toggleCount += delta; if (summaryPtr->toggleCount > 0) { goto nextAncestor; } if (summaryPtr->toggleCount < 0) { panic("ChangeNodeToggleCount: negative toggle count"); } /* * Zero count; must remove this tag from the list. */ if (prevPtr == NULL) { nodePtr->summaryPtr = summaryPtr->nextPtr; } else { prevPtr->nextPtr = summaryPtr->nextPtr; } ckfree((char *) summaryPtr); goto nextAncestor; } /* * This tag isn't in the list. Add a new entry to the list. */ if (delta < 0) { panic("ChangeNodeToggleCount: negative delta, no tag entry"); } summaryPtr = (Summary *) ckalloc(sizeof(Summary)); summaryPtr->tagPtr = tagPtr; summaryPtr->toggleCount = delta; summaryPtr->nextPtr = nodePtr->summaryPtr; nodePtr->summaryPtr = summaryPtr; nextAncestor: continue; } } /* *---------------------------------------------------------------------- * * TkBTreeStartSearch -- * * This procedure sets up a search for tag transitions involving * a given tag (or all tags) in a given range of the text. * * Results: * None. * * Side effects: * The information at *searchPtr is set up so that subsequent calls * to TkBTreeNextTag will return information about the locations of * tag transitions. Note that TkBTreeNextTag must be called to get * the first transition. * *---------------------------------------------------------------------- */ void TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, searchPtr) TkTextIndex *index1Ptr; /* Search starts here. Tag toggles * at this position will not be * returned. */ TkTextIndex *index2Ptr; /* Search stops here. Tag toggles * at this position *will* be * returned. */ TkTextTag *tagPtr; /* Tag to search for. NULL means * search for any tag. */ register TkTextSearch *searchPtr; /* Where to store information about * search's progress. */ { int offset; searchPtr->curIndex = *index1Ptr; searchPtr->segPtr = NULL; searchPtr->nextPtr = TkTextIndexToSeg(index1Ptr, &offset); searchPtr->curIndex.charIndex -= offset; searchPtr->lastPtr = TkTextIndexToSeg(index2Ptr, (int *) NULL); searchPtr->tagPtr = tagPtr; searchPtr->linesLeft = TkBTreeLineIndex(index2Ptr->linePtr) + 1 - TkBTreeLineIndex(index1Ptr->linePtr); searchPtr->allTags = (tagPtr == NULL); if (searchPtr->linesLeft == 1) { /* * Starting and stopping segments are in the same line; mark the * search as over immediately if the second segment is before the * first. */ if (index1Ptr->charIndex >= index2Ptr->charIndex) { searchPtr->linesLeft = 0; } } } /* *---------------------------------------------------------------------- * * TkBTreeNextTag -- * * Once a tag search has begun, successive calls to this procedure * return successive tag toggles. Note: it is NOT SAFE to call this * procedure if characters have been inserted into or deleted from * the B-tree since the call to TkBTreeStartSearch. * * Results: * The return value is 1 if another toggle was found that met the * criteria specified in the call to TkBTreeStartSearch; in this * case searchPtr->curIndex gives the toggle's position and * searchPtr->curTagPtr points to its segment. 0 is returned if * no more matching tag transitions were found; in this case * searchPtr->curIndex is the same as searchPtr->stopIndex. * * Side effects: * Information in *searchPtr is modified to update the state of the * search and indicate where the next tag toggle is located. * *---------------------------------------------------------------------- */ int TkBTreeNextTag(searchPtr) register TkTextSearch *searchPtr; /* Information about search in * progress; must have been set up by * call to TkBTreeStartSearch. */ { register TkTextSegment *segPtr; register Node *nodePtr; register Summary *summaryPtr; if (searchPtr->linesLeft <= 0) { goto searchOver; } /* * The outermost loop iterates over lines that may potentially contain * a relevant tag transition, starting from the current segment in * the current line. */ segPtr = searchPtr->nextPtr; while (1) { /* * Check for more tags on the current line. */ for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { if (segPtr == searchPtr->lastPtr) { goto searchOver; } if (((segPtr->typePtr == &tkTextToggleOnType) || (segPtr->typePtr == &tkTextToggleOffType)) && (searchPtr->allTags || (segPtr->body.toggle.tagPtr == searchPtr->tagPtr))) { searchPtr->segPtr = segPtr; searchPtr->nextPtr = segPtr->nextPtr; searchPtr->tagPtr = segPtr->body.toggle.tagPtr; return 1; } searchPtr->curIndex.charIndex += segPtr->size; } /* * See if there are more lines associated with the current parent * node. If so, go back to the top of the loop to search the next * one. */ nodePtr = searchPtr->curIndex.linePtr->parentPtr; searchPtr->curIndex.linePtr = searchPtr->curIndex.linePtr->nextPtr; searchPtr->linesLeft--; if (searchPtr->linesLeft <= 0) { goto searchOver; } if (searchPtr->curIndex.linePtr != NULL) { segPtr = searchPtr->curIndex.linePtr->segPtr; searchPtr->curIndex.charIndex = 0; continue; } /* * Search across and up through the B-tree's node hierarchy looking * for the next node that has a relevant tag transition somewhere in * its subtree. Be sure to update linesLeft as we skip over large * chunks of lines. */ while (1) { while (nodePtr->nextPtr == NULL) { if (nodePtr->parentPtr == NULL) { goto searchOver; } nodePtr = nodePtr->parentPtr; } nodePtr = nodePtr->nextPtr; for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { if ((searchPtr->allTags) || (summaryPtr->tagPtr == searchPtr->tagPtr)) { goto gotNodeWithTag; } } searchPtr->linesLeft -= nodePtr->numLines; } /* * At this point we've found a subtree that has a relevant tag * transition. Now search down (and across) through that subtree * to find the first level-0 node that has a relevant tag transition. */ gotNodeWithTag: while (nodePtr->level > 0) { for (nodePtr = nodePtr->children.nodePtr; ; nodePtr = nodePtr->nextPtr) { for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { if ((searchPtr->allTags) || (summaryPtr->tagPtr == searchPtr->tagPtr)) { goto nextChild; } } searchPtr->linesLeft -= nodePtr->numLines; if (nodePtr->nextPtr == NULL) { panic("TkBTreeNextTag found incorrect tag summary info."); } } nextChild: continue; } /* * Now we're down to a level-0 node that contains a line that contains * a relevant tag transition. Set up line information and go back to * the beginning of the loop to search through lines. */ searchPtr->curIndex.linePtr = nodePtr->children.linePtr; searchPtr->curIndex.charIndex = 0; segPtr = searchPtr->curIndex.linePtr->segPtr; if (searchPtr->linesLeft <= 0) { goto searchOver; } continue; } searchOver: searchPtr->linesLeft = 0; searchPtr->segPtr = NULL; return 0; } /* *---------------------------------------------------------------------- * * TkBTreeCharTagged -- * * Determine whether a particular character has a particular tag. * * Results: * The return value is 1 if the given tag is in effect at the * character given by linePtr and ch, and 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkBTreeCharTagged(indexPtr, tagPtr) TkTextIndex *indexPtr; /* Indicates a character position at * which to check for a tag. */ TkTextTag *tagPtr; /* Tag of interest. */ { register Node *nodePtr; register TkTextLine *siblingLinePtr; register TkTextSegment *segPtr; TkTextSegment *toggleSegPtr; int toggles, index; /* * Check for toggles for the tag in indexPtr's line but before * indexPtr. If there is one, its type indicates whether or * not the character is tagged. */ toggleSegPtr = NULL; for (index = 0, segPtr = indexPtr->linePtr->segPtr; (index + segPtr->size) <= indexPtr->charIndex; index += segPtr->size, segPtr = segPtr->nextPtr) { if (((segPtr->typePtr == &tkTextToggleOnType) || (segPtr->typePtr == &tkTextToggleOffType)) && (segPtr->body.toggle.tagPtr == tagPtr)) { toggleSegPtr = segPtr; } } if (toggleSegPtr != NULL) { return (toggleSegPtr->typePtr == &tkTextToggleOnType); } /* * No toggle in this line. Look for toggles for the tag in lines * that are predecessors of indexPtr->linePtr but under the same * level-0 node. */ toggles = 0; for (siblingLinePtr = indexPtr->linePtr->parentPtr->children.linePtr; siblingLinePtr != indexPtr->linePtr; siblingLinePtr = siblingLinePtr->nextPtr) { for (segPtr = siblingLinePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { if (((segPtr->typePtr == &tkTextToggleOnType) || (segPtr->typePtr == &tkTextToggleOffType)) && (segPtr->body.toggle.tagPtr == tagPtr)) { toggleSegPtr = segPtr; } } } if (toggleSegPtr != NULL) { return (toggleSegPtr->typePtr == &tkTextToggleOnType); } /* * No toggle in this node. Scan upwards through the ancestors of * this node, counting the number of toggles of the given tag in * siblings that precede that node. */ toggles = 0; for (nodePtr = indexPtr->linePtr->parentPtr; nodePtr->parentPtr != NULL; nodePtr = nodePtr->parentPtr) { register Node *siblingPtr; register Summary *summaryPtr; for (siblingPtr = nodePtr->parentPtr->children.nodePtr; siblingPtr != nodePtr; siblingPtr = siblingPtr->nextPtr) { for (summaryPtr = siblingPtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr->tagPtr == tagPtr) { toggles += summaryPtr->toggleCount; } } } } /* * An odd number of toggles means that the tag is present at the * given point. */ return toggles & 1; } /* *---------------------------------------------------------------------- * * TkBTreeGetTags -- * * Return information about all of the tags that are associated * with a particular character in a B-tree of text. * * Results: * The return value is a malloc-ed array containing pointers to * information for each of the tags that is associated with * the character at the position given by linePtr and ch. The * word at *numTagsPtr is filled in with the number of pointers * in the array. It is up to the caller to free the array by * passing it to free. If there are no tags at the given character * then a NULL pointer is returned and *numTagsPtr will be set to 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ /* ARGSUSED */ TkTextTag ** TkBTreeGetTags(indexPtr, numTagsPtr) TkTextIndex *indexPtr; /* Indicates a particular position in * the B-tree. */ int *numTagsPtr; /* Store number of tags found at this * location. */ { register Node *nodePtr; register TkTextLine *siblingLinePtr; register TkTextSegment *segPtr; int src, dst, index; TagInfo tagInfo; #define NUM_TAG_INFOS 10 tagInfo.numTags = 0; tagInfo.arraySize = NUM_TAG_INFOS; tagInfo.tagPtrs = (TkTextTag **) ckalloc((unsigned) NUM_TAG_INFOS*sizeof(TkTextTag *)); tagInfo.counts = (int *) ckalloc((unsigned) NUM_TAG_INFOS*sizeof(int)); /* * Record tag toggles within the line of indexPtr but preceding * indexPtr. */ for (index = 0, segPtr = indexPtr->linePtr->segPtr; (index + segPtr->size) <= indexPtr->charIndex; index += segPtr->size, segPtr = segPtr->nextPtr) { if ((segPtr->typePtr == &tkTextToggleOnType) || (segPtr->typePtr == &tkTextToggleOffType)) { IncCount(segPtr->body.toggle.tagPtr, 1, &tagInfo); } } /* * Record toggles for tags in lines that are predecessors of * indexPtr->linePtr but under the same level-0 node. */ for (siblingLinePtr = indexPtr->linePtr->parentPtr->children.linePtr; siblingLinePtr != indexPtr->linePtr; siblingLinePtr = siblingLinePtr->nextPtr) { for (segPtr = siblingLinePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { if ((segPtr->typePtr == &tkTextToggleOnType) || (segPtr->typePtr == &tkTextToggleOffType)) { IncCount(segPtr->body.toggle.tagPtr, 1, &tagInfo); } } } /* * For each node in the ancestry of this line, record tag toggles * for all siblings that precede that node. */ for (nodePtr = indexPtr->linePtr->parentPtr; nodePtr->parentPtr != NULL; nodePtr = nodePtr->parentPtr) { register Node *siblingPtr; register Summary *summaryPtr; for (siblingPtr = nodePtr->parentPtr->children.nodePtr; siblingPtr != nodePtr; siblingPtr = siblingPtr->nextPtr) { for (summaryPtr = siblingPtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr->toggleCount & 1) { IncCount(summaryPtr->tagPtr, summaryPtr->toggleCount, &tagInfo); } } } } /* * Go through the tag information and squash out all of the tags * that have even toggle counts (these tags exist before the point * of interest, but not at the desired character itself). */ for (src = 0, dst = 0; src < tagInfo.numTags; src++) { if (tagInfo.counts[src] & 1) { tagInfo.tagPtrs[dst] = tagInfo.tagPtrs[src]; dst++; } } *numTagsPtr = dst; ckfree((char *) tagInfo.counts); if (dst == 0) { ckfree((char *) tagInfo.tagPtrs); return NULL; } return tagInfo.tagPtrs; } /* *---------------------------------------------------------------------- * * IncCount -- * * This is a utility procedure used by TkBTreeGetTags. It * increments the count for a particular tag, adding a new * entry for that tag if there wasn't one previously. * * Results: * None. * * Side effects: * The information at *tagInfoPtr may be modified, and the arrays * may be reallocated to make them larger. * *---------------------------------------------------------------------- */ static void IncCount(tagPtr, inc, tagInfoPtr) TkTextTag *tagPtr; /* Handle for tag. */ int inc; /* Amount by which to increment tag count. */ TagInfo *tagInfoPtr; /* Holds cumulative information about tags; * increment count here. */ { register TkTextTag **tagPtrPtr; int count; for (tagPtrPtr = tagInfoPtr->tagPtrs, count = tagInfoPtr->numTags; count > 0; tagPtrPtr++, count--) { if (*tagPtrPtr == tagPtr) { tagInfoPtr->counts[tagInfoPtr->numTags-count] += inc; return; } } /* * There isn't currently an entry for this tag, so we have to * make a new one. If the arrays are full, then enlarge the * arrays first. */ if (tagInfoPtr->numTags == tagInfoPtr->arraySize) { TkTextTag **newTags; int *newCounts, newSize; newSize = 2*tagInfoPtr->arraySize; newTags = (TkTextTag **) ckalloc((unsigned) (newSize*sizeof(TkTextTag *))); memcpy((VOID *) newTags, (VOID *) tagInfoPtr->tagPtrs, tagInfoPtr->arraySize * sizeof(TkTextTag *)); ckfree((char *) tagInfoPtr->tagPtrs); tagInfoPtr->tagPtrs = newTags; newCounts = (int *) ckalloc((unsigned) (newSize*sizeof(int))); memcpy((VOID *) newCounts, (VOID *) tagInfoPtr->counts, tagInfoPtr->arraySize * sizeof(int)); ckfree((char *) tagInfoPtr->counts); tagInfoPtr->counts = newCounts; tagInfoPtr->arraySize = newSize; } tagInfoPtr->tagPtrs[tagInfoPtr->numTags] = tagPtr; tagInfoPtr->counts[tagInfoPtr->numTags] = inc; tagInfoPtr->numTags++; } /* *---------------------------------------------------------------------- * * TkBTreeCheck -- * * This procedure runs a set of consistency checks over a B-tree * and panics if any inconsistencies are found. * * Results: * None. * * Side effects: * If a structural defect is found, the procedure panics with an * error message. * *---------------------------------------------------------------------- */ void TkBTreeCheck(tree) TkTextBTree tree; /* Tree to check. */ { BTree *treePtr = (BTree *) tree; register Summary *summaryPtr; register Node *nodePtr; register TkTextLine *linePtr; register TkTextSegment *segPtr; /* * Make sure that overall there is an even count of tag transitions * for the whole tree. */ for (summaryPtr = treePtr->rootPtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr->toggleCount & 1) { panic("TkBTreeCheck found odd toggle count for \"%s\" (%d)", summaryPtr->tagPtr->name, summaryPtr->toggleCount); } } /* * Call a recursive procedure to do the main body of checks. */ nodePtr = treePtr->rootPtr; CheckNodeConsistency(treePtr->rootPtr); /* * Make sure that there are at least two lines in the text and * that the last line has no characters except a newline. */ if (nodePtr->numLines < 2) { panic("TkBTreeCheck: less than 2 lines in tree"); } while (nodePtr->level > 0) { nodePtr = nodePtr->children.nodePtr; while (nodePtr->nextPtr != NULL) { nodePtr = nodePtr->nextPtr; } } linePtr = nodePtr->children.linePtr; while (linePtr->nextPtr != NULL) { linePtr = linePtr->nextPtr; } segPtr = linePtr->segPtr; while ((segPtr->typePtr == &tkTextToggleOffType) || (segPtr->typePtr == &tkTextRightMarkType) || (segPtr->typePtr == &tkTextLeftMarkType)) { /* * It's OK to toggle a tag off in the last line, but * not to start a new range. It's also OK to have marks * in the last line. */ segPtr = segPtr->nextPtr; } if (segPtr->typePtr != &tkTextCharType) { panic("TkBTreeCheck: last line has bogus segment type"); } if (segPtr->nextPtr != NULL) { panic("TkBTreeCheck: last line has too many segments"); } if (segPtr->size != 1) { panic("TkBTreeCheck: last line has wrong # characters: %d", segPtr->size); } if ((segPtr->body.chars[0] != '\n') || (segPtr->body.chars[1] != 0)) { panic("TkBTreeCheck: last line had bad value: %s", segPtr->body.chars); } } /* *---------------------------------------------------------------------- * * CheckNodeConsistency -- * * This procedure is called as part of consistency checking for * B-trees: it checks several aspects of a node and also runs * checks recursively on the node's children. * * Results: * None. * * Side effects: * If anything suspicious is found in the tree structure, the * procedure panics. * *---------------------------------------------------------------------- */ static void CheckNodeConsistency(nodePtr) register Node *nodePtr; /* Node whose subtree should be * checked. */ { register Node *childNodePtr; register Summary *summaryPtr, *summaryPtr2; register TkTextLine *linePtr; register TkTextSegment *segPtr; int numChildren, numLines, toggleCount, minChildren; if (nodePtr->parentPtr != NULL) { minChildren = MIN_CHILDREN; } else if (nodePtr->level > 0) { minChildren = 2; } else { minChildren = 1; } if ((nodePtr->numChildren < minChildren) || (nodePtr->numChildren > MAX_CHILDREN)) { panic("CheckNodeConsistency: bad child count (%d)", nodePtr->numChildren); } numChildren = 0; numLines = 0; if (nodePtr->level == 0) { for (linePtr = nodePtr->children.linePtr; linePtr != NULL; linePtr = linePtr->nextPtr) { if (linePtr->parentPtr != nodePtr) { panic("CheckNodeConsistency: line doesn't point to parent"); } if (linePtr->segPtr == NULL) { panic("CheckNodeConsistency: line has no segments"); } for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { if (segPtr->typePtr->checkProc != NULL) { (*segPtr->typePtr->checkProc)(segPtr, linePtr); } if ((segPtr->size == 0) && (!segPtr->typePtr->leftGravity) && (segPtr->nextPtr != NULL) && (segPtr->nextPtr->size == 0) && (segPtr->nextPtr->typePtr->leftGravity)) { panic("CheckNodeConsistency: wrong segment order for gravity"); } if ((segPtr->nextPtr == NULL) && (segPtr->typePtr != &tkTextCharType)) { panic("CheckNodeConsistency: line ended with wrong type"); } } numChildren++; numLines++; } } else { for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL; childNodePtr = childNodePtr->nextPtr) { if (childNodePtr->parentPtr != nodePtr) { panic("CheckNodeConsistency: node doesn't point to parent"); } if (childNodePtr->level != (nodePtr->level-1)) { panic("CheckNodeConsistency: level mismatch (%d %d)", nodePtr->level, childNodePtr->level); } CheckNodeConsistency(childNodePtr); for (summaryPtr = childNodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { for (summaryPtr2 = nodePtr->summaryPtr; ; summaryPtr2 = summaryPtr2->nextPtr) { if (summaryPtr2 == NULL) { panic("CheckNodeConsistency: node tag \"%s\" not %s", summaryPtr->tagPtr->name, "present in parent summaries"); } if (summaryPtr->tagPtr == summaryPtr2->tagPtr) { break; } } } numChildren++; numLines += childNodePtr->numLines; } } if (numChildren != nodePtr->numChildren) { panic("CheckNodeConsistency: mismatch in numChildren (%d %d)", numChildren, nodePtr->numChildren); } if (numLines != nodePtr->numLines) { panic("CheckNodeConsistency: mismatch in numLines (%d %d)", numLines, nodePtr->numLines); } for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { toggleCount = 0; if (nodePtr->level == 0) { for (linePtr = nodePtr->children.linePtr; linePtr != NULL; linePtr = linePtr->nextPtr) { for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { if ((segPtr->typePtr != &tkTextToggleOnType) && (segPtr->typePtr != &tkTextToggleOffType)) { continue; } if (segPtr->body.toggle.tagPtr == summaryPtr->tagPtr) { toggleCount ++; } } } } else { for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL; childNodePtr = childNodePtr->nextPtr) { for (summaryPtr2 = childNodePtr->summaryPtr; summaryPtr2 != NULL; summaryPtr2 = summaryPtr2->nextPtr) { if (summaryPtr2->tagPtr == summaryPtr->tagPtr) { toggleCount += summaryPtr2->toggleCount; } } } } if (toggleCount != summaryPtr->toggleCount) { panic("CheckNodeConsistency: mismatch in toggleCount (%d %d)", toggleCount, summaryPtr->toggleCount); } for (summaryPtr2 = summaryPtr->nextPtr; summaryPtr2 != NULL; summaryPtr2 = summaryPtr2->nextPtr) { if (summaryPtr2->tagPtr == summaryPtr->tagPtr) { panic("CheckNodeConsistency: duplicated node tag: %s", summaryPtr->tagPtr->name); } } } } /* *---------------------------------------------------------------------- * * Rebalance -- * * This procedure is called when a node of a B-tree appears to be * out of balance (too many children, or too few). It rebalances * that node and all of its ancestors in the tree. * * Results: * None. * * Side effects: * The internal structure of treePtr may change. * *---------------------------------------------------------------------- */ static void Rebalance(treePtr, nodePtr) BTree *treePtr; /* Tree that is being rebalanced. */ register Node *nodePtr; /* Node that may be out of balance. */ { /* * Loop over the entire ancestral chain of the node, working up * through the tree one node at a time until the root node has * been processed. */ for ( ; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { register Node *newPtr, *childPtr; register TkTextLine *linePtr; int i; /* * Check to see if the node has too many children. If it does, * then split off all but the first MIN_CHILDREN into a separate * node following the original one. Then repeat until the * node has a decent size. */ if (nodePtr->numChildren > MAX_CHILDREN) { while (1) { /* * If the node being split is the root node, then make a * new root node above it first. */ if (nodePtr->parentPtr == NULL) { newPtr = (Node *) ckalloc(sizeof(Node)); newPtr->parentPtr = NULL; newPtr->nextPtr = NULL; newPtr->summaryPtr = NULL; newPtr->level = nodePtr->level + 1; newPtr->children.nodePtr = nodePtr; newPtr->numChildren = 1; newPtr->numLines = nodePtr->numLines; RecomputeNodeCounts(newPtr); treePtr->rootPtr = newPtr; } newPtr = (Node *) ckalloc(sizeof(Node)); newPtr->parentPtr = nodePtr->parentPtr; newPtr->nextPtr = nodePtr->nextPtr; nodePtr->nextPtr = newPtr; newPtr->summaryPtr = NULL; newPtr->level = nodePtr->level; newPtr->numChildren = nodePtr->numChildren - MIN_CHILDREN; if (nodePtr->level == 0) { for (i = MIN_CHILDREN-1, linePtr = nodePtr->children.linePtr; i > 0; i--, linePtr = linePtr->nextPtr) { /* Empty loop body. */ } newPtr->children.linePtr = linePtr->nextPtr; linePtr->nextPtr = NULL; } else { for (i = MIN_CHILDREN-1, childPtr = nodePtr->children.nodePtr; i > 0; i--, childPtr = childPtr->nextPtr) { /* Empty loop body. */ } newPtr->children.nodePtr = childPtr->nextPtr; childPtr->nextPtr = NULL; } RecomputeNodeCounts(nodePtr); nodePtr->parentPtr->numChildren++; nodePtr = newPtr; if (nodePtr->numChildren <= MAX_CHILDREN) { RecomputeNodeCounts(nodePtr); break; } } } while (nodePtr->numChildren < MIN_CHILDREN) { register Node *otherPtr; Node *halfwayNodePtr = NULL; /* Initialization needed only */ TkTextLine *halfwayLinePtr = NULL; /* to prevent cc warnings. */ int totalChildren, firstChildren, i; /* * Too few children for this node. If this is the root then, * it's OK for it to have less than MIN_CHILDREN children * as long as it's got at least two. If it has only one * (and isn't at level 0), then chop the root node out of * the tree and use its child as the new root. */ if (nodePtr->parentPtr == NULL) { if ((nodePtr->numChildren == 1) && (nodePtr->level > 0)) { treePtr->rootPtr = nodePtr->children.nodePtr; treePtr->rootPtr->parentPtr = NULL; DeleteSummaries(nodePtr->summaryPtr); ckfree((char *) nodePtr); } return; } /* * Not the root. Make sure that there are siblings to * balance with. */ if (nodePtr->parentPtr->numChildren < 2) { Rebalance(treePtr, nodePtr->parentPtr); continue; } /* * Find a sibling neighbor to borrow from, and arrange for * nodePtr to be the earlier of the pair. */ if (nodePtr->nextPtr == NULL) { for (otherPtr = nodePtr->parentPtr->children.nodePtr; otherPtr->nextPtr != nodePtr; otherPtr = otherPtr->nextPtr) { /* Empty loop body. */ } nodePtr = otherPtr; } otherPtr = nodePtr->nextPtr; /* * We're going to either merge the two siblings together * into one node or redivide the children among them to * balance their loads. As preparation, join their two * child lists into a single list and remember the half-way * point in the list. */ totalChildren = nodePtr->numChildren + otherPtr->numChildren; firstChildren = totalChildren/2; if (nodePtr->children.nodePtr == NULL) { nodePtr->children = otherPtr->children; otherPtr->children.nodePtr = NULL; otherPtr->children.linePtr = NULL; } if (nodePtr->level == 0) { register TkTextLine *linePtr; for (linePtr = nodePtr->children.linePtr, i = 1; linePtr->nextPtr != NULL; linePtr = linePtr->nextPtr, i++) { if (i == firstChildren) { halfwayLinePtr = linePtr; } } linePtr->nextPtr = otherPtr->children.linePtr; while (i <= firstChildren) { halfwayLinePtr = linePtr; linePtr = linePtr->nextPtr; i++; } } else { register Node *childPtr; for (childPtr = nodePtr->children.nodePtr, i = 1; childPtr->nextPtr != NULL; childPtr = childPtr->nextPtr, i++) { if (i <= firstChildren) { if (i == firstChildren) { halfwayNodePtr = childPtr; } } } childPtr->nextPtr = otherPtr->children.nodePtr; while (i <= firstChildren) { halfwayNodePtr = childPtr; childPtr = childPtr->nextPtr; i++; } } /* * If the two siblings can simply be merged together, do it. */ if (totalChildren <= MAX_CHILDREN) { RecomputeNodeCounts(nodePtr); nodePtr->nextPtr = otherPtr->nextPtr; nodePtr->parentPtr->numChildren--; DeleteSummaries(otherPtr->summaryPtr); ckfree((char *) otherPtr); continue; } /* * The siblings can't be merged, so just divide their * children evenly between them. */ if (nodePtr->level == 0) { otherPtr->children.linePtr = halfwayLinePtr->nextPtr; halfwayLinePtr->nextPtr = NULL; } else { otherPtr->children.nodePtr = halfwayNodePtr->nextPtr; halfwayNodePtr->nextPtr = NULL; } RecomputeNodeCounts(nodePtr); RecomputeNodeCounts(otherPtr); } } } /* *---------------------------------------------------------------------- * * RecomputeNodeCounts -- * * This procedure is called to recompute all the counts in a node * (tags, child information, etc.) by scanning the information in * its descendants. This procedure is called during rebalancing * when a node's child structure has changed. * * Results: * None. * * Side effects: * The tag counts for nodePtr are modified to reflect its current * child structure, as are its numChildren and numLines fields. * Also, all of the childrens' parentPtr fields are made to point * to nodePtr. * *---------------------------------------------------------------------- */ static void RecomputeNodeCounts(nodePtr) register Node *nodePtr; /* Node whose tag summary information * must be recomputed. */ { register Summary *summaryPtr, *summaryPtr2; register Node *childPtr; register TkTextLine *linePtr; register TkTextSegment *segPtr; TkTextTag *tagPtr; /* * Zero out all the existing counts for the node, but don't delete * the existing Summary records (most of them will probably be reused). */ for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { summaryPtr->toggleCount = 0; } nodePtr->numChildren = 0; nodePtr->numLines = 0; /* * Scan through the children, adding the childrens' tag counts into * the node's tag counts and adding new Summary structures if * necessary. */ if (nodePtr->level == 0) { for (linePtr = nodePtr->children.linePtr; linePtr != NULL; linePtr = linePtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines++; linePtr->parentPtr = nodePtr; for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { if (((segPtr->typePtr != &tkTextToggleOnType) && (segPtr->typePtr != &tkTextToggleOffType)) || !(segPtr->body.toggle.inNodeCounts)) { continue; } tagPtr = segPtr->body.toggle.tagPtr; for (summaryPtr = nodePtr->summaryPtr; ; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr == NULL) { summaryPtr = (Summary *) ckalloc(sizeof(Summary)); summaryPtr->tagPtr = tagPtr; summaryPtr->toggleCount = 1; summaryPtr->nextPtr = nodePtr->summaryPtr; nodePtr->summaryPtr = summaryPtr; break; } if (summaryPtr->tagPtr == tagPtr) { summaryPtr->toggleCount++; break; } } } } } else { for (childPtr = nodePtr->children.nodePtr; childPtr != NULL; childPtr = childPtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines += childPtr->numLines; childPtr->parentPtr = nodePtr; for (summaryPtr2 = childPtr->summaryPtr; summaryPtr2 != NULL; summaryPtr2 = summaryPtr2->nextPtr) { for (summaryPtr = nodePtr->summaryPtr; ; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr == NULL) { summaryPtr = (Summary *) ckalloc(sizeof(Summary)); summaryPtr->tagPtr = summaryPtr2->tagPtr; summaryPtr->toggleCount = summaryPtr2->toggleCount; summaryPtr->nextPtr = nodePtr->summaryPtr; nodePtr->summaryPtr = summaryPtr; break; } if (summaryPtr->tagPtr == summaryPtr2->tagPtr) { summaryPtr->toggleCount += summaryPtr2->toggleCount; break; } } } } } /* * Scan through the node's tag records again and delete any Summary * records that still have a zero count. */ summaryPtr2 = NULL; for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; ) { if (summaryPtr->toggleCount > 0) { summaryPtr2 = summaryPtr; summaryPtr = summaryPtr->nextPtr; continue; } if (summaryPtr2 != NULL) { summaryPtr2->nextPtr = summaryPtr->nextPtr; ckfree((char *) summaryPtr); summaryPtr = summaryPtr2->nextPtr; } else { nodePtr->summaryPtr = summaryPtr->nextPtr; ckfree((char *) summaryPtr); summaryPtr = nodePtr->summaryPtr; } } } /* *---------------------------------------------------------------------- * * TkBTreeNumLines -- * * This procedure returns a count of the number of lines of * text present in a given B-tree. * * Results: * The return value is a count of the number of usable lines * in tree (i.e. it doesn't include the dummy line that is just * used to mark the end of the tree). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkBTreeNumLines(tree) TkTextBTree tree; /* Information about tree. */ { BTree *treePtr = (BTree *) tree; return treePtr->rootPtr->numLines - 1; } /* *-------------------------------------------------------------- * * CharSplitProc -- * * 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 TkTextSegment * CharSplitProc(segPtr, index) TkTextSegment *segPtr; /* Pointer to segment to split. */ int index; /* Position within segment at which * to split. */ { TkTextSegment *newPtr1, *newPtr2; newPtr1 = (TkTextSegment *) ckalloc(CSEG_SIZE(index)); newPtr2 = (TkTextSegment *) ckalloc( CSEG_SIZE(segPtr->size - index)); newPtr1->typePtr = &tkTextCharType; newPtr1->nextPtr = newPtr2; newPtr1->size = index; strncpy(newPtr1->body.chars, segPtr->body.chars, (size_t) index); newPtr1->body.chars[index] = 0; newPtr2->typePtr = &tkTextCharType; newPtr2->nextPtr = segPtr->nextPtr; newPtr2->size = segPtr->size - index; strcpy(newPtr2->body.chars, segPtr->body.chars + index); ckfree((char*) segPtr); return newPtr1; } /* *-------------------------------------------------------------- * * CharCleanupProc -- * * 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 TkTextSegment * CharCleanupProc(segPtr, linePtr) TkTextSegment *segPtr; /* Pointer to first of two adjacent * segments to join. */ TkTextLine *linePtr; /* Line containing segments (not * used). */ { TkTextSegment *segPtr2, *newPtr; segPtr2 = segPtr->nextPtr; if ((segPtr2 == NULL) || (segPtr2->typePtr != &tkTextCharType)) { return segPtr; } newPtr = (TkTextSegment *) ckalloc(CSEG_SIZE( segPtr->size + segPtr2->size)); newPtr->typePtr = &tkTextCharType; newPtr->nextPtr = segPtr2->nextPtr; newPtr->size = segPtr->size + segPtr2->size; strcpy(newPtr->body.chars, segPtr->body.chars); strcpy(newPtr->body.chars + segPtr->size, segPtr2->body.chars); ckfree((char*) segPtr); ckfree((char*) segPtr2); return newPtr; } /* *-------------------------------------------------------------- * * CharDeleteProc -- * * 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 CharDeleteProc(segPtr, linePtr, treeGone) TkTextSegment *segPtr; /* Segment to delete. */ TkTextLine *linePtr; /* Line containing segment. */ int treeGone; /* Non-zero means the entire tree is * being deleted, so everything must * get cleaned up. */ { ckfree((char*) segPtr); return 0; } /* *-------------------------------------------------------------- * * CharCheckProc -- * * This procedure is invoked to perform consistency checks * on character segments. * * Results: * None. * * Side effects: * If the segment isn't inconsistent then the procedure * panics. * *-------------------------------------------------------------- */ /* ARGSUSED */ static void CharCheckProc(segPtr, linePtr) TkTextSegment *segPtr; /* Segment to check. */ TkTextLine *linePtr; /* Line containing segment. */ { /* * Make sure that the segment contains the number of * characters indicated by its header, and that the last * segment in a line ends in a newline. Also make sure * that there aren't ever two character segments adjacent * to each other: they should be merged together. */ if (segPtr->size <= 0) { panic("CharCheckProc: segment has size <= 0"); } if (strlen(segPtr->body.chars) != segPtr->size) { panic("CharCheckProc: segment has wrong size"); } if (segPtr->nextPtr == NULL) { if (segPtr->body.chars[segPtr->size-1] != '\n') { panic("CharCheckProc: line doesn't end with newline"); } } else { if (segPtr->nextPtr->typePtr == &tkTextCharType) { panic("CharCheckProc: adjacent character segments weren't merged"); } } } /* *-------------------------------------------------------------- * * ToggleDeleteProc -- * * 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 nodes above the * segment get updated. * *-------------------------------------------------------------- */ static int ToggleDeleteProc(segPtr, linePtr, treeGone) TkTextSegment *segPtr; /* Segment to check. */ TkTextLine *linePtr; /* Line containing segment. */ int treeGone; /* Non-zero means the entire tree is * being deleted, so everything must * get cleaned up. */ { if (treeGone) { ckfree((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 node toggle counts here, and set a flag * so we'll re-increment them in the cleanup procedure. */ if (segPtr->body.toggle.inNodeCounts) { ChangeNodeToggleCount(linePtr->parentPtr, segPtr->body.toggle.tagPtr, -1); segPtr->body.toggle.inNodeCounts = 0; } return 1; } /* *-------------------------------------------------------------- * * ToggleCleanupProc -- * * This procedure 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 nodes 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 TkTextSegment * ToggleCleanupProc(segPtr, linePtr) TkTextSegment *segPtr; /* Segment to check. */ TkTextLine *linePtr; /* Line that now contains segment. */ { TkTextSegment *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->typePtr == &tkTextToggleOffType) { for (prevPtr = segPtr, segPtr2 = prevPtr->nextPtr; (segPtr2 != NULL) && (segPtr2->size == 0); prevPtr = segPtr2, segPtr2 = prevPtr->nextPtr) { if (segPtr2->typePtr != &tkTextToggleOnType) { continue; } if (segPtr2->body.toggle.tagPtr != segPtr->body.toggle.tagPtr) { continue; } counts = segPtr->body.toggle.inNodeCounts + segPtr2->body.toggle.inNodeCounts; if (counts != 0) { ChangeNodeToggleCount(linePtr->parentPtr, segPtr->body.toggle.tagPtr, -counts); } prevPtr->nextPtr = segPtr2->nextPtr; ckfree((char *) segPtr2); segPtr2 = segPtr->nextPtr; ckfree((char *) segPtr); return segPtr2; } } if (!segPtr->body.toggle.inNodeCounts) { ChangeNodeToggleCount(linePtr->parentPtr, segPtr->body.toggle.tagPtr, 1); segPtr->body.toggle.inNodeCounts = 1; } return segPtr; } /* *-------------------------------------------------------------- * * ToggleLineChangeProc -- * * 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 nodes above the line. * *-------------------------------------------------------------- */ static void ToggleLineChangeProc(segPtr, linePtr) TkTextSegment *segPtr; /* Segment to check. */ TkTextLine *linePtr; /* Line that used to contain segment. */ { if (segPtr->body.toggle.inNodeCounts) { ChangeNodeToggleCount(linePtr->parentPtr, segPtr->body.toggle.tagPtr, -1); segPtr->body.toggle.inNodeCounts = 0; } } /* *-------------------------------------------------------------- * * ToggleCheckProc -- * * This procedure is invoked to perform consistency checks * on toggle segments. * * Results: * None. * * Side effects: * If a consistency problem is found the procedure panics. * *-------------------------------------------------------------- */ static void ToggleCheckProc(segPtr, linePtr) TkTextSegment *segPtr; /* Segment to check. */ TkTextLine *linePtr; /* Line containing segment. */ { register Summary *summaryPtr; if (segPtr->size != 0) { panic("ToggleCheckProc: segment had non-zero size"); } if (!segPtr->body.toggle.inNodeCounts) { panic("ToggleCheckProc: toggle counts not updated in nodes"); } for (summaryPtr = linePtr->parentPtr->summaryPtr; ; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr == NULL) { panic("ToggleCheckProc: tag not present in node"); } if (summaryPtr->tagPtr == segPtr->body.toggle.tagPtr) { break; } } } /* *---------------------------------------------------------------------- * * TkBTreeCharsInLine -- * * This procedure returns a count of the number of characters * in a given line. * * Results: * The return value is the character count for linePtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkBTreeCharsInLine(linePtr) TkTextLine *linePtr; /* Line whose characters should be * counted. */ { TkTextSegment *segPtr; int count; count = 0; for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { count += segPtr->size; } return count; }