2384 lines
66 KiB
C
2384 lines
66 KiB
C
|
/*
|
|||
|
* 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;
|
|||
|
}
|