/* * tkTextBTree.c -- * * This file contains code that manages the B-tree representation * of text for Tk's text widget. The B-tree holds both the text * and tag information related to the text. * * Copyright (c) 1992-1993 The Regents of the University of California. * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ #ifndef lint static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkTextBTree.c,v 1.19 93/10/11 16:55:21 ouster Exp $ SPRITE (Berkeley)"; #endif /* not lint */ #include "tkInt.h" #include "tkConfig.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 representing * all of the lines in a text widget. */ typedef struct Node { struct Node *parentPtr; /* Pointer to parent node, or NULL if * this is the root. */ struct Node *nextPtr; /* Next in list of children of 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; /* * Macro to compute the space needed for a line that holds n non-null * characters: */ #define LINE_SIZE(n) ((unsigned) (sizeof(TkTextLine) - 3 + (n))) /* * Variable that indicates whether to enable consistency checks for * debugging. */ int tkBTreeDebug = 0; /* * Forward declarations for procedures defined in this file: */ static void AddToggleToLine _ANSI_ARGS_((TkTextLine *linePtr, int index, TkTextTag *tagPtr)); static void ChangeNodeToggleCount _ANSI_ARGS_((Node *nodePtr, TkTextTag *tagPtr, int delta)); static void CheckNodeConsistency _ANSI_ARGS_((Node *nodePtr)); 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)); /* *---------------------------------------------------------------------- * * 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; rootPtr = (Node *) ckalloc(sizeof(Node)); linePtr = (TkTextLine *) ckalloc(LINE_SIZE(1)); rootPtr->parentPtr = NULL; rootPtr->nextPtr = NULL; rootPtr->summaryPtr = NULL; rootPtr->level = 0; rootPtr->children.linePtr = linePtr; rootPtr->numChildren = 1; rootPtr->numLines = 1; linePtr->parentPtr = rootPtr; linePtr->nextPtr = NULL; linePtr->annotPtr = NULL; linePtr->numBytes = 1; linePtr->bytes[0] = '\n'; linePtr->bytes[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) { register TkTextLine *curPtr, *nextLinePtr; register TkAnnotation *annotPtr, *nextAnnotPtr; for (curPtr = nodePtr->children.linePtr; curPtr != NULL; ) { nextLinePtr = curPtr->nextPtr; for (annotPtr = curPtr->annotPtr; annotPtr != NULL; ) { nextAnnotPtr = annotPtr->nextPtr; if (annotPtr->type == TK_ANNOT_TOGGLE) { ckfree((char *) annotPtr); } annotPtr = nextAnnotPtr; } ckfree((char *) curPtr); curPtr = nextLinePtr; } } else { register Node *curPtr, *nextPtr; for (curPtr = nodePtr->children.nodePtr; curPtr != NULL; ) { nextPtr = curPtr->nextPtr; DestroyNode(curPtr); curPtr = nextPtr; } } 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: * NumBytes characters are added to the B-tree at the given * character position. This can cause the structure of the * B-tree to change. * *---------------------------------------------------------------------- */ void TkBTreeInsertChars(tree, linePtr, ch, string) TkTextBTree tree; /* B-tree in which to insert. */ register TkTextLine *linePtr; /* Pointer to line in which to * insert. */ int ch; /* Index of character before which * to insert. Must not be after * last character in line.*/ char *string; /* Pointer to bytes to insert (may * contain newlines, must be null- * terminated). */ { BTree *treePtr = (BTree *) tree; register Node *nodePtr; register TkAnnotation *annotPtr; TkTextLine *prevPtr; int newChunkLength; /* # chars in current line being * inserted. */ register char *eol; /* Pointer to last character in * current line being inserted. */ int changeToLineCount; /* Counts change to total number of * lines in file. */ TkAnnotation *afterPtr; /* List of annotations that occur * at or after the insertion point * in the line of the insertion. */ int prefixLength, suffixLength, totalLength; register TkTextLine *newPtr; /* * Find the line just before the one where the insertion will occur * but with the same parent node (if there is one). This is needed * so we can replace the insertion line with a new one. Remove this * line from the list for its parent, since it's going to be discarded * when we're all done). */ nodePtr = linePtr->parentPtr; prevPtr = nodePtr->children.linePtr; if (prevPtr == linePtr) { prevPtr = NULL; nodePtr->children.linePtr = linePtr->nextPtr; } else { for ( ; prevPtr->nextPtr != linePtr; prevPtr = prevPtr->nextPtr) { /* Empty loop body. */ } prevPtr->nextPtr = linePtr->nextPtr; } /* * Break up the annotations for the insertion line into two pieces: * those before the insertion point, and those at or after the insertion * point. */ afterPtr = NULL; if ((linePtr->annotPtr != NULL) && (linePtr->annotPtr->ch >= ch)) { afterPtr = linePtr->annotPtr; linePtr->annotPtr = NULL; } else { for (annotPtr = linePtr->annotPtr; annotPtr != NULL; annotPtr = annotPtr->nextPtr) { if ((annotPtr->nextPtr != NULL) && (annotPtr->nextPtr->ch >= ch)) { afterPtr = annotPtr->nextPtr; annotPtr->nextPtr = NULL; break; } } } /* * Chop the string up into lines and insert each line individually. */ changeToLineCount = -1; prefixLength = ch; while (1) { for (newChunkLength = 0, eol = string; *eol != 0; eol++) { newChunkLength++; if (*eol == '\n') { break; } } /* * Create a new line consisting of up to three parts: a prefix * from linePtr, some material from string, and a suffix from * linePtr. */ if ((newChunkLength == 0) || (*eol != '\n')) { suffixLength = linePtr->numBytes - ch; } else { suffixLength = 0; } totalLength = prefixLength + newChunkLength + suffixLength; newPtr = (TkTextLine *) ckalloc(LINE_SIZE(totalLength)); newPtr->parentPtr = nodePtr; if (prevPtr == NULL) { newPtr->nextPtr = nodePtr->children.linePtr; nodePtr->children.linePtr = newPtr; } else { newPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = newPtr; } if (linePtr->annotPtr != NULL) { newPtr->annotPtr = linePtr->annotPtr; for (annotPtr = newPtr->annotPtr; annotPtr != NULL; annotPtr = annotPtr->nextPtr) { annotPtr->linePtr = newPtr; } linePtr->annotPtr = NULL; } else { newPtr->annotPtr = NULL; } newPtr->numBytes = totalLength; if (prefixLength != 0) { memcpy((VOID *) newPtr->bytes, (VOID *) linePtr->bytes, prefixLength); } if (newChunkLength != 0) { memcpy((VOID *) (newPtr->bytes + prefixLength), (VOID *) string, newChunkLength); } if (suffixLength != 0) { memcpy((VOID *) (newPtr->bytes + prefixLength + newChunkLength), (VOID *) (linePtr->bytes + ch), suffixLength); } newPtr->bytes[totalLength] = 0; changeToLineCount += 1; /* * Quit after the suffix has been output (there is always at least * one character of suffix: the newline). Before jumping out of the * loop, put back the annotations that pertain to the suffix. * Careful! If no newlines were inserted, there could already be * annotations at the beginning of the line; add back to the end. */ if (suffixLength != 0) { if (newPtr->annotPtr == NULL) { newPtr->annotPtr = afterPtr; } else { for (annotPtr = newPtr->annotPtr; annotPtr->nextPtr != NULL; annotPtr = annotPtr->nextPtr) { /* Empty loop body. */ } annotPtr->nextPtr = afterPtr; } for (annotPtr = afterPtr; annotPtr != NULL; annotPtr = annotPtr->nextPtr) { annotPtr->linePtr = newPtr; annotPtr->ch += prefixLength+newChunkLength-ch; } break; } /* * Advance to insert the next line chunk. */ string += newChunkLength; prefixLength = 0; prevPtr = newPtr; } /* * Increment the line counts in all the parent nodes of the insertion * point, then rebalance the tree if necessary. */ for ( ; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines += changeToLineCount; } nodePtr = linePtr->parentPtr; nodePtr->numChildren += changeToLineCount; if (nodePtr->numChildren > MAX_CHILDREN) { Rebalance(treePtr, nodePtr); } ckfree((char *) linePtr); if (tkBTreeDebug) { TkBTreeCheck(tree); } } /* *---------------------------------------------------------------------- * * TkBTreeDeleteChars -- * * Delete a range of characters from a B-tree. * * Results: * None. * * Side effects: * Information is deleted from the B-tree. This can cause the * internal structure of the B-tree to change. Note: the two * lines given by line1Ptr and line2Ptr will be replaced with * a single line containing the undeleted parts of the original * lines. This could potentially result in an empty line; * normally the caller should adjust the deletion range to prevent * this sort of behavior. * *---------------------------------------------------------------------- */ void TkBTreeDeleteChars(tree, line1Ptr, ch1, line2Ptr, ch2) TkTextBTree tree; /* B-tree in which to delete. */ register TkTextLine *line1Ptr; /* Line containing first character * to delete. */ int ch1; /* Index within linePtr1 of first * character to delete. */ register TkTextLine *line2Ptr; /* Line containing character just * after last one to delete. */ int ch2; /* Index within linePtr2 of character * just after last one to delete. */ { BTree *treePtr = (BTree *) tree; TkTextLine *linePtr, *nextPtr, *prevLinePtr; Node *nodePtr, *parentPtr, *nextNodePtr; TkAnnotation *annotPtr, *annotPtr2; int ch; int linesDeleted; /* Counts lines deleted from current * level-0 node. */ /* * Work through the tree deleting all of the lines between line1Ptr * and line2Ptr (but don't delete line1Ptr or line2Ptr yet). Also * delete any nodes in the B-tree that become empty because of * this process. */ linePtr = line1Ptr->nextPtr; nodePtr = line1Ptr->parentPtr; if (line1Ptr == line2Ptr) { goto middleLinesDeleted; } while (1) { /* * Delete all relevant lines within the same level-0 node. */ linesDeleted = 0; while ((linePtr != line2Ptr) && (linePtr != NULL)) { /* * Move any annotations in this line to the end of the * deletion range. If both the starting and ending toggle * for a tagged range get moved, they'll cancel each other * automatically and be dropped, which is the right behavior. */ for (annotPtr = linePtr->annotPtr, linePtr->annotPtr = NULL; annotPtr != NULL; annotPtr = annotPtr2) { if (annotPtr->type == TK_ANNOT_TOGGLE) { AddToggleToLine(line2Ptr, ch2, annotPtr->info.tagPtr); ChangeNodeToggleCount(nodePtr, annotPtr->info.tagPtr, -1); annotPtr2 = annotPtr->nextPtr; ckfree((char *) annotPtr); } else { annotPtr2 = annotPtr->nextPtr; annotPtr->linePtr = line2Ptr; annotPtr->ch = ch2; TkBTreeAddAnnotation(annotPtr); } } nextPtr = linePtr->nextPtr; ckfree((char *) linePtr); linesDeleted++; linePtr = nextPtr; } if (nodePtr == line1Ptr->parentPtr) { line1Ptr->nextPtr = linePtr; } else { nodePtr->children.linePtr = linePtr; } for (parentPtr = nodePtr; parentPtr != NULL; parentPtr = parentPtr->parentPtr) { parentPtr->numLines -= linesDeleted; } nodePtr->numChildren -= linesDeleted; if (linePtr == line2Ptr) { break; } /* * Find the next level-0 node to visit, and its first line (but * remember the current node so we can come back to delete it if * it's empty). */ nextNodePtr = nodePtr; while (nextNodePtr->nextPtr == NULL) { nextNodePtr = nextNodePtr->parentPtr; } nextNodePtr = nextNodePtr->nextPtr; while (nextNodePtr->level > 0) { nextNodePtr = nextNodePtr->children.nodePtr; } linePtr = nextNodePtr->children.linePtr; /* * Now go back to the node we just left and delete it if * it's empty, along with any of its ancestors that are * empty. It may seem funny to go back like this, but it's * simpler to find the next place to visit before modifying * the tree structure. */ while (nodePtr->numChildren == 0) { parentPtr = nodePtr->parentPtr; if (parentPtr->children.nodePtr == nodePtr) { parentPtr->children.nodePtr = nodePtr->nextPtr; } else { Node *prevPtr; for (prevPtr = parentPtr->children.nodePtr; prevPtr->nextPtr != nodePtr; prevPtr = prevPtr->nextPtr) { } prevPtr->nextPtr = nodePtr->nextPtr; } parentPtr->numChildren--; DeleteSummaries(nodePtr->summaryPtr); ckfree((char *) nodePtr); nodePtr = parentPtr; } nodePtr = nextNodePtr; } /* * Make a new line that consists of the first part of the first * line of the deletion range and the last part of the last line * of the deletion range. */ middleLinesDeleted: nodePtr = line1Ptr->parentPtr; linePtr = (TkTextLine *) ckalloc(LINE_SIZE(ch1 + line2Ptr->numBytes - ch2)); linePtr->parentPtr = nodePtr; linePtr->nextPtr = line1Ptr->nextPtr; linePtr->annotPtr = NULL; linePtr->numBytes = ch1 + line2Ptr->numBytes - ch2; if (ch1 != 0) { memcpy((VOID *) linePtr->bytes, (VOID *) line1Ptr->bytes, ch1); } strcpy(linePtr->bytes + ch1, line2Ptr->bytes + ch2); /* * Process the annotations for the starting and ending lines. Enter * a new annotation on linePtr (the joined line) for each of these * annotations, then delete the originals. The code below is a little * tricky (e.g. the "break" in the first loop) to handle the case where * the starting and ending lines are the same. */ for (annotPtr = line1Ptr->annotPtr; annotPtr != NULL; annotPtr = line1Ptr->annotPtr) { if (annotPtr->ch <= ch1) { ch = annotPtr->ch; } else { if (line1Ptr == line2Ptr) { break; } ch = ch1; } line1Ptr->annotPtr = annotPtr->nextPtr; if (annotPtr->type == TK_ANNOT_TOGGLE) { AddToggleToLine(linePtr, ch, annotPtr->info.tagPtr); ChangeNodeToggleCount(line1Ptr->parentPtr, annotPtr->info.tagPtr, -1); ckfree((char *) annotPtr); } else { annotPtr->linePtr = linePtr; annotPtr->ch = ch; TkBTreeAddAnnotation(annotPtr); } } for (annotPtr = line2Ptr->annotPtr; annotPtr != NULL; annotPtr = line2Ptr->annotPtr) { if (annotPtr->ch >= ch2) { ch = annotPtr->ch - ch2 + ch1; } else { ch = ch1; } line2Ptr->annotPtr = annotPtr->nextPtr; if (annotPtr->type == TK_ANNOT_TOGGLE) { AddToggleToLine(linePtr, ch, annotPtr->info.tagPtr); ChangeNodeToggleCount(line2Ptr->parentPtr, annotPtr->info.tagPtr, -1); ckfree((char *) annotPtr); } else { annotPtr->linePtr = linePtr; annotPtr->ch = ch; TkBTreeAddAnnotation(annotPtr); } } /* * Delete the original starting and stopping lines (don't forget * that the annotations have already been deleted) and insert the * new line in place of line1Ptr. */ nodePtr = line1Ptr->parentPtr; if (nodePtr->children.linePtr == line1Ptr) { nodePtr->children.linePtr = linePtr; } else { for (prevLinePtr = nodePtr->children.linePtr; prevLinePtr->nextPtr != line1Ptr; prevLinePtr = prevLinePtr->nextPtr) { /* Empty loop body. */ } prevLinePtr->nextPtr = linePtr; } ckfree((char *) line1Ptr); if (line2Ptr != line1Ptr) { nodePtr = line2Ptr->parentPtr; if (nodePtr->children.linePtr == line2Ptr) { nodePtr->children.linePtr = line2Ptr->nextPtr; } else { for (prevLinePtr = nodePtr->children.linePtr; prevLinePtr->nextPtr != line2Ptr; prevLinePtr = prevLinePtr->nextPtr) { /* Empty loop body. */ } prevLinePtr->nextPtr = line2Ptr->nextPtr; } ckfree((char *) line2Ptr); for (parentPtr = nodePtr; parentPtr != NULL; parentPtr = parentPtr->parentPtr) { parentPtr->numLines--; } nodePtr->numChildren--; } /* * Rebalance the tree, starting from each of the endpoints of the * deletion range. This code is a tricky, because the act of * rebalancing the parent of one endpoint can cause the parent of * the other endpoint to be reallocated. The only thing it's safe * to hold onto is a pointer to a line. Thus, rebalance line2Ptr's * parent first, then use linePtr find the second parent to rebalance * second. */ if (nodePtr != linePtr->parentPtr) { Rebalance(treePtr, nodePtr); } Rebalance(treePtr, linePtr->parentPtr); if (tkBTreeDebug) { TkBTreeCheck(tree); } } /* *---------------------------------------------------------------------- * * 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. * *---------------------------------------------------------------------- */ void TkBTreeTag(tree, line1, ch1, line2, ch2, tagPtr, add) TkTextBTree tree; /* B-tree in which to add tag * information. */ int line1, ch1; /* Position of first character to * tag. */ int line2, ch2; /* Position of character just after * last one to tag. */ TkTextTag *tagPtr; /* Tag to associate with the range * of characters. */ int add; /* One means add tag to the given * range of characters; zero means * remove the tag from the range. */ { BTree *treePtr = (BTree *) tree; register TkTextLine *line1Ptr, *line2Ptr; TkTextSearch search; int oldState; /* * Find the lines containing the first and last characters to be tagged, * and adjust the starting and stopping locations if they don't already * point within lines. If the range would have started or stopped at the * end of a line, round it up to the beginning of the next line (right * now this restriction keeps the final newline from being tagged). */ if (line1 < 0) { line1 = 0; ch1 = 0; } line1Ptr = TkBTreeFindLine(tree, line1); if (line1Ptr == NULL) { return; } if (ch1 >= line1Ptr->numBytes) { TkTextLine *nextLinePtr; nextLinePtr = TkBTreeNextLine(line1Ptr); if (nextLinePtr == NULL) { return; } else { line1Ptr = nextLinePtr; line1++; ch1 = 0; } } if (line2 < 0) { return; } line2Ptr = TkBTreeFindLine(tree, line2); if (line2Ptr == NULL) { line2Ptr = TkBTreeFindLine(tree, treePtr->rootPtr->numLines-1); ch2 = line2Ptr->numBytes-1; } if (ch2 >= line2Ptr->numBytes) { TkTextLine *nextLinePtr; nextLinePtr = TkBTreeNextLine(line2Ptr); if (nextLinePtr == NULL) { ch2 = line2Ptr->numBytes-1; } else { line2Ptr = nextLinePtr; line2++; ch2 = 0; } } /* * See if the tag is already present or absent at the start of the * range. If the state doesn't already match what we want then add * a toggle there. */ oldState = TkBTreeCharTagged(line1Ptr, ch1, tagPtr); if ((add != 0) ^ oldState) { AddToggleToLine(line1Ptr, ch1, tagPtr); } /* * Scan the range of characters covered by the change and delete * any existing tag transitions except those on the first and * last characters. Keep track of whether the old state just before * the last character (not including any tags on it) is what we * want now; if not, then add a tag toggle there. */ TkBTreeStartSearch(tree, line1, ch1+1, line2, ch2, tagPtr, &search); while (TkBTreeNextTag(&search)) { if ((search.linePtr == line2Ptr) && (search.ch1 == ch2)) { break; } oldState ^= 1; AddToggleToLine(search.linePtr, search.ch1, tagPtr); } if ((add != 0) ^ oldState) { AddToggleToLine(line2Ptr, ch2, tagPtr); } if (tkBTreeDebug) { TkBTreeCheck(tree); } } /* *---------------------------------------------------------------------- * * TkBTreeAddAnnotation -- * * Given a filled in annotation, this procedure links it into * a B-tree structure so that it will track changes to the B-tree. * * Results: * None. * * Side effects: * AnnotPtr will be linked into its tree. Note: the storage for * annotPtr is assumed to have been malloc'ed by the caller. * *---------------------------------------------------------------------- */ /* ARGSUSED */ void TkBTreeAddAnnotation(annotPtr) TkAnnotation *annotPtr; /* Pointer to annotation. The caller must * have filled in all the fields except the * "nextPtr" field. The type should NOT be * TK_ANNOT_TOGGLE; these annotations are * managed by the TkBTreeTag procedure. */ { register TkAnnotation *annotPtr2, *prevPtr; for (prevPtr = NULL, annotPtr2 = annotPtr->linePtr->annotPtr; annotPtr2 != NULL; prevPtr = annotPtr2, annotPtr2 = annotPtr2->nextPtr) { if (annotPtr2->ch > annotPtr->ch) { break; } } if (prevPtr == NULL) { annotPtr->nextPtr = annotPtr->linePtr->annotPtr; annotPtr->linePtr->annotPtr = annotPtr; } else { annotPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = annotPtr; } } /* *---------------------------------------------------------------------- * * TkBTreeRemoveAnnotation -- * * This procedure unlinks an annotation from a B-tree so that * the annotation will no longer be managed by the B-tree code. * * Results: * None. * * Side effects: * AnnotPtr will be unlinked from its tree. Note: it is up to the * caller to free the storage for annotPtr, if that is desired. * *---------------------------------------------------------------------- */ /* ARGSUSED */ void TkBTreeRemoveAnnotation(annotPtr) TkAnnotation *annotPtr; /* Pointer to annotation, which must * have been linked into tree by a previous * call to TkBTreeAddAnnotation. */ { register TkAnnotation *prevPtr; if (annotPtr->linePtr->annotPtr == annotPtr) { annotPtr->linePtr->annotPtr = annotPtr->nextPtr; } else { for (prevPtr = annotPtr->linePtr->annotPtr; prevPtr->nextPtr != annotPtr; prevPtr = prevPtr->nextPtr) { /* Empty loop body. */ } prevPtr->nextPtr = annotPtr->nextPtr; } } /* *---------------------------------------------------------------------- * * 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; } /* *---------------------------------------------------------------------- * * 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(tree, line1, ch1, line2, ch2, tagPtr, searchPtr) TkTextBTree tree; /* Tree to search. */ int line1, ch1; /* Character position at which to * start search (tags at this position * will be returned). */ int line2, ch2; /* Character position at which to * stop search (tags 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. */ { register TkAnnotation *annotPtr; searchPtr->tree = tree; if (line1 < 0) { searchPtr->line1 = 0; searchPtr->ch1 = 0; } else { searchPtr->line1 = line1; searchPtr->ch1 = ch1; } searchPtr->line2 = line2; searchPtr->ch2 = ch2; searchPtr->tagPtr = tagPtr; searchPtr->allTags = (tagPtr == NULL); searchPtr->linePtr = TkBTreeFindLine(searchPtr->tree, searchPtr->line1); if (searchPtr->linePtr == NULL) { searchPtr->line1 = searchPtr->line2; searchPtr->ch1 = searchPtr->ch2; searchPtr->annotPtr = NULL; } else { for (annotPtr = searchPtr->linePtr->annotPtr; (annotPtr != NULL) && (annotPtr->ch < ch1); annotPtr = annotPtr->nextPtr) { /* Empty loop body. */ } searchPtr->annotPtr = annotPtr; } } /* *---------------------------------------------------------------------- * * 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. 0 is * returned if no more matching tag transitions were found. * * 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 TkAnnotation *annotPtr; register Node *nodePtr; register Summary *summaryPtr; if (searchPtr->linePtr == NULL) { return 0; } /* * The outermost loop iterates over lines that may potentially contain * a relevant tag transition, starting from the current line and tag. */ while (1) { /* * See if there are more tags on the current line that are relevant. */ for (annotPtr = searchPtr->annotPtr; annotPtr != NULL; annotPtr = annotPtr->nextPtr) { if ((annotPtr->type == TK_ANNOT_TOGGLE) && (searchPtr->allTags || (annotPtr->info.tagPtr == searchPtr->tagPtr))) { if ((searchPtr->line1 == searchPtr->line2) && (annotPtr->ch > searchPtr->ch2)) { goto searchOver; } searchPtr->tagPtr = annotPtr->info.tagPtr; searchPtr->ch1 = annotPtr->ch; searchPtr->annotPtr = annotPtr->nextPtr; return 1; } } /* * 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 of them. */ if (searchPtr->line1 >= searchPtr->line2) { goto searchOver; } searchPtr->line1++; if (searchPtr->linePtr->nextPtr != NULL) { searchPtr->linePtr = searchPtr->linePtr->nextPtr; searchPtr->annotPtr = searchPtr->linePtr->annotPtr; 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 the current line number as we * skip over large chunks of lines. */ nodePtr = searchPtr->linePtr->parentPtr; 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->line1 += 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->line1 += 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->linePtr = nodePtr->children.linePtr; searchPtr->annotPtr = searchPtr->linePtr->annotPtr; if (searchPtr->line1 > searchPtr->line2) { goto searchOver; } continue; } searchOver: searchPtr->line1 = searchPtr->line2; searchPtr->ch1 = searchPtr->ch2; searchPtr->annotPtr = NULL; searchPtr->linePtr = NULL; return 0; } /* *---------------------------------------------------------------------- * * 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; /* * Make sure that overall there is an even count of tag transitions * for the whole text. */ 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 all of the rest of the checks. */ CheckNodeConsistency(treePtr->rootPtr); } /* *---------------------------------------------------------------------- * * 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, * 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 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 scaning 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 children's 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 TkAnnotation *annotPtr; /* * 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 Summarys to the node if * necessary. */ if (nodePtr->level == 0) { for (linePtr = nodePtr->children.linePtr; linePtr != NULL; linePtr = linePtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines++; linePtr->parentPtr = nodePtr; for (annotPtr = linePtr->annotPtr; annotPtr != NULL; annotPtr = annotPtr->nextPtr) { if (annotPtr->type != TK_ANNOT_TOGGLE) { continue; } for (summaryPtr = nodePtr->summaryPtr; ; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr == NULL) { summaryPtr = (Summary *) ckalloc(sizeof(Summary)); summaryPtr->tagPtr = annotPtr->info.tagPtr; summaryPtr->toggleCount = 1; summaryPtr->nextPtr = nodePtr->summaryPtr; nodePtr->summaryPtr = summaryPtr; break; } if (summaryPtr->tagPtr == annotPtr->info.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; } } } /* *---------------------------------------------------------------------- * * AddToggleToLine -- * * Insert a tag transition at a particular point in a particular * line. * * Results: * None. * * Side effects: * LinePtr and all its ancestors in the B-tree stucture are modified * to indicate the presence of a transition (either on or off) on * tag at the given place in the given line. * *---------------------------------------------------------------------- */ static void AddToggleToLine(linePtr, index, tagPtr) TkTextLine *linePtr; /* Line within which to add * transition. */ int index; /* Character before which to * add transition. */ TkTextTag *tagPtr; /* Information about tag. */ { register TkAnnotation *annotPtr, *prevPtr; int delta = 1; /* * Find the position where the toggle should be inserted into * the array (just after prevPtr), and see if there is already * a toggle at exactly the point where we're going to insert a * new toggle. If so then the two toggles cancel; just delete * the existing toggle. */ for (prevPtr = NULL, annotPtr = linePtr->annotPtr; annotPtr != NULL; prevPtr = annotPtr, annotPtr = annotPtr->nextPtr) { if (annotPtr->ch > index) { break; } if ((annotPtr->type == TK_ANNOT_TOGGLE) && (annotPtr->ch == index) && (annotPtr->info.tagPtr == tagPtr)) { if (prevPtr == NULL) { linePtr->annotPtr = annotPtr->nextPtr; } else { prevPtr->nextPtr = annotPtr->nextPtr; } ckfree((char *) annotPtr); delta = -1; goto updateNodes; } } /* * Create a new toggle and insert it into the list. */ annotPtr = (TkAnnotation *) ckalloc(sizeof(TkAnnotation)); annotPtr->type = TK_ANNOT_TOGGLE; annotPtr->linePtr = linePtr; annotPtr->ch = index; annotPtr->info.tagPtr = tagPtr; if (prevPtr == NULL) { annotPtr->nextPtr = linePtr->annotPtr; linePtr->annotPtr = annotPtr; } else { annotPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = annotPtr; } /* * Update all the nodes above this line to reflect the change in * toggle structure. */ updateNodes: ChangeNodeToggleCount(linePtr->parentPtr, tagPtr, delta); } /* *---------------------------------------------------------------------- * * 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; } } /* *---------------------------------------------------------------------- * * 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(linePtr, ch, tagPtr) TkTextLine *linePtr; /* Line containing character of * interest. */ int ch; /* Index of character in linePtr. */ TkTextTag *tagPtr; /* Tag of interest. */ { register Node *nodePtr; register TkTextLine *siblingLinePtr; int toggles; /* * Count the number of toggles for the tag at the line level (i.e. * in all the sibling lines that precede this one, plus in this line * up to the character of interest. */ toggles = 0; for (siblingLinePtr = linePtr->parentPtr->children.linePtr; ; siblingLinePtr = siblingLinePtr->nextPtr) { register TkAnnotation *annotPtr; for (annotPtr = siblingLinePtr->annotPtr; (annotPtr != NULL) && ((siblingLinePtr != linePtr) || (annotPtr->ch <= ch)); annotPtr = annotPtr->nextPtr) { if ((annotPtr->type == TK_ANNOT_TOGGLE) && (annotPtr->info.tagPtr == tagPtr)) { toggles++; } } if (siblingLinePtr == linePtr) { break; } } /* * For each node in the ancestry of this line, count the number of * toggles of the given tag in siblings that precede that node. */ for (nodePtr = 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(tree, linePtr, ch, numTagsPtr) TkTextBTree tree; /* Tree to check. */ TkTextLine *linePtr; /* Line containing character of interest. */ int ch; /* Index within linePtr of character for * which tag information is wanted. */ int *numTagsPtr; /* Store number of tags found at this * location. */ { register Node *nodePtr; register TkTextLine *siblingLinePtr; int src, dst; 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 at the line level (i.e. in all the sibling * lines that precede this one, plus in this line up to the character * of interest. */ for (siblingLinePtr = linePtr->parentPtr->children.linePtr; ; siblingLinePtr = siblingLinePtr->nextPtr) { register TkAnnotation *annotPtr; for (annotPtr = siblingLinePtr->annotPtr; (annotPtr != NULL) && ((siblingLinePtr != linePtr) || (annotPtr->ch <= ch)); annotPtr = annotPtr->nextPtr) { if (annotPtr->type == TK_ANNOT_TOGGLE) { IncCount(annotPtr->info.tagPtr, 1, &tagInfo); } } if (siblingLinePtr == linePtr) { break; } } /* * For each node in the ancestry of this line, record tag toggles * for all siblings that precede that node. */ for (nodePtr = 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) { 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++; } /* *---------------------------------------------------------------------- * * 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 TkAnnotation *annotPtr; register TkTextLine *linePtr; register char *p; int numChildren, numLines, toggleCount, minChildren, index, numBytes; 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 found 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 found line that %s", "didn't point to parent"); } for (p = linePtr->bytes, numBytes = 0; *p != 0; p++, numBytes++) { if ((*p == '\n') && (numBytes != linePtr->numBytes-1)) { panic("CheckNodeConsistency found line with extra newline"); } } if (numBytes != linePtr->numBytes) { panic("CheckNodeConsistency found line with bad numBytes"); } if (linePtr->bytes[numBytes-1] != '\n') { panic("CheckNodeConsistency found line with no newline"); } index = 0; for (annotPtr = linePtr->annotPtr; annotPtr != NULL; annotPtr = annotPtr->nextPtr) { if (annotPtr->ch < index) { panic("CheckNodeConsistency found %s (%d %d)", "out-of-order tag indices", index, annotPtr->ch); } index = annotPtr->ch; if (annotPtr->type == TK_ANNOT_TOGGLE) { for (summaryPtr = nodePtr->summaryPtr; ; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr == NULL) { panic("CheckNodeConsistency found line %s", "tag with no node tag: %s", summaryPtr->tagPtr->name); } if (summaryPtr->tagPtr == annotPtr->info.tagPtr) { break; } } } } numChildren++; numLines++; } } else { for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL; childNodePtr = childNodePtr->nextPtr) { CheckNodeConsistency(childNodePtr); for (summaryPtr = childNodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { for (summaryPtr2 = nodePtr->summaryPtr; ; summaryPtr2 = summaryPtr2->nextPtr) { if (summaryPtr2 == NULL) { panic("CheckNodeConsistency found %s (%s)", "node tag with no parent tag", summaryPtr->tagPtr->name); } if (summaryPtr->tagPtr == summaryPtr2->tagPtr) { break; } } } numChildren++; numLines += childNodePtr->numLines; if (childNodePtr->parentPtr != nodePtr) { panic("CheckNodeConsistency found node that %s", "didn't point to parent"); } if (childNodePtr->level != (nodePtr->level-1)) { panic("CheckNodeConsistency found level mismatch (%d %d)", nodePtr->level, childNodePtr->level); } } } if (numChildren != nodePtr->numChildren) { panic("CheckNodeConsistency found mismatch in numChildren (%d %d)", numChildren, nodePtr->numChildren); } if (numLines != nodePtr->numLines) { panic("CheckNodeConsistency found 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 (annotPtr = linePtr->annotPtr; annotPtr != NULL; annotPtr = annotPtr->nextPtr) { if (annotPtr->info.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 found mismatch in toggleCount (%d %d)", toggleCount, summaryPtr->toggleCount); } for (summaryPtr2 = summaryPtr->nextPtr; summaryPtr2 != NULL; summaryPtr2 = summaryPtr2->nextPtr) { if (summaryPtr2->tagPtr == summaryPtr->tagPtr) { panic("CheckNodeConsistency found duplicated node tag: %s", summaryPtr->tagPtr->name); } } } } /* *---------------------------------------------------------------------- * * 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 lines in tree. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkBTreeNumLines(tree) TkTextBTree tree; /* Information about tree. */ { BTree *treePtr = (BTree *) tree; return treePtr->rootPtr->numLines; }