Files
cpython-source-deps/generic/tkTextBTree.c
2021-11-08 17:29:19 +00:00

4905 lines
138 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* tkTextBTree.c --
*
* This file contains code that manages the B-tree representation of text
* for Tk's text widget and implements character and toggle segment
* types.
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1995 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tkInt.h"
#include "tkText.h"
/*
* Implementation notes:
*
* Most of this file is independent of the text widget implementation and
* representation now. Without much effort this could be developed further
* into a new Tcl object type of which the Tk text widget is one example of a
* client.
*
* The B-tree is set up with a dummy last line of text which must not be
* displayed, and must _never_ have a non-zero pixel count. This dummy line is
* a historical convenience to avoid other code having to deal with NULL
* TkTextLines. Since Tk 8.5, with pixel line height calculations and peer
* widgets, this dummy line is becoming somewhat of a liability, and special
* case code has been required to deal with it. It is probably a good idea to
* investigate removing the dummy line completely. This could result in an
* overall simplification (although it would require new special case code to
* deal with the fact that '.text index end' would then not really point to a
* valid line, rather it would point to the beginning of a non-existent line
* one beyond all current lines - we could perhaps define that as a
* TkTextIndex with a NULL TkTextLine ptr).
*/
/*
* The data structure below keeps summary information about one tag as part of
* the tag information in a node.
*/
typedef struct Summary {
TkTextTag *tagPtr; /* Handle for tag. */
int toggleCount; /* Number of transitions into or out of this
* tag that occur in the subtree rooted at
* this node. */
struct Summary *nextPtr; /* Next in list of all tags for same node, or
* NULL if at end of list. */
} Summary;
/*
* The data structure below defines a node in the B-tree.
*/
typedef struct Node {
struct Node *parentPtr; /* Pointer to parent node, or NULL if this is
* the root. */
struct Node *nextPtr; /* Next in list of siblings with the same
* parent node, or NULL for end of list. */
Summary *summaryPtr; /* First in malloc-ed list of info about tags
* in this subtree (NULL if no tag info in the
* subtree). */
int level; /* Level of this node in the B-tree. 0 refers
* to the bottom of the tree (children are
* lines, not nodes). */
union { /* First in linked list of children. */
struct Node *nodePtr; /* Used if level > 0. */
TkTextLine *linePtr; /* Used if level == 0. */
} children;
int numChildren; /* Number of children of this node. */
int numLines; /* Total number of lines (leaves) in the
* subtree rooted here. */
int *numPixels; /* Array containing total number of vertical
* display pixels in the subtree rooted here,
* one entry for each peer widget. */
} Node;
/*
* Used to avoid having to allocate and deallocate arrays on the fly for
* commonly used functions. Must be > 0.
*/
#define PIXEL_CLIENTS 5
/*
* 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. Since text widgets are
* the only current B-tree clients, 'clients' and 'pixelReferences' are
* identical.
*/
typedef struct BTree {
Node *rootPtr; /* Pointer to root of B-tree. */
int clients; /* Number of clients of this B-tree. */
int pixelReferences; /* Number of clients of this B-tree which care
* about pixel heights. */
int stateEpoch; /* Updated each time any aspect of the B-tree
* changes. */
TkSharedText *sharedTextPtr;/* Used to find tagTable in consistency
* checking code, and to access list of all
* B-tree clients. */
int startEndCount;
TkTextLine **startEnd;
TkText **startEndRef;
} BTree;
/*
* The structure below is used to pass information between
* TkBTreeGetTags and IncCount:
*/
typedef struct TagInfo {
int numTags; /* Number of tags for which there is currently
* information in tags and counts. */
int arraySize; /* Number of entries allocated for tags and
* counts. */
TkTextTag **tagPtrs; /* Array of tags seen so far. Malloc-ed. */
int *counts; /* Toggle count (so far) for each entry in
* tags. Malloc-ed. */
} TagInfo;
/*
* Variable that indicates whether to enable consistency checks for debugging.
*/
int tkBTreeDebug = 0;
/*
* Macros that determine how much space to allocate for new segments:
*/
#define CSEG_SIZE(chars) ((unsigned)(Tk_Offset(TkTextSegment, body) \
+ 1 + (chars)))
#define TSEG_SIZE ((unsigned)(Tk_Offset(TkTextSegment, body) \
+ sizeof(TkTextToggle)))
/*
* Forward declarations for functions defined in this file:
*/
static int AdjustPixelClient(BTree *treePtr, int defaultHeight,
Node *nodePtr, TkTextLine *start, TkTextLine *end,
int useReference, int newPixelReferences,
int *counting);
static void ChangeNodeToggleCount(Node *nodePtr,
TkTextTag *tagPtr, int delta);
static void CharCheckProc(TkTextSegment *segPtr,
TkTextLine *linePtr);
static int CharDeleteProc(TkTextSegment *segPtr,
TkTextLine *linePtr, int treeGone);
static TkTextSegment * CharCleanupProc(TkTextSegment *segPtr,
TkTextLine *linePtr);
static TkTextSegment * CharSplitProc(TkTextSegment *segPtr, int index);
static void CheckNodeConsistency(Node *nodePtr, int references);
static void CleanupLine(TkTextLine *linePtr);
static void DeleteSummaries(Summary *tagPtr);
static void DestroyNode(Node *nodePtr);
static TkTextSegment * FindTagEnd(TkTextBTree tree, TkTextTag *tagPtr,
TkTextIndex *indexPtr);
static void IncCount(TkTextTag *tagPtr, int inc,
TagInfo *tagInfoPtr);
static void Rebalance(BTree *treePtr, Node *nodePtr);
static void RecomputeNodeCounts(BTree *treePtr, Node *nodePtr);
static void RemovePixelClient(BTree *treePtr, Node *nodePtr,
int overwriteWithLast);
static TkTextSegment * SplitSeg(TkTextIndex *indexPtr);
static void ToggleCheckProc(TkTextSegment *segPtr,
TkTextLine *linePtr);
static TkTextSegment * ToggleCleanupProc(TkTextSegment *segPtr,
TkTextLine *linePtr);
static int ToggleDeleteProc(TkTextSegment *segPtr,
TkTextLine *linePtr, int treeGone);
static void ToggleLineChangeProc(TkTextSegment *segPtr,
TkTextLine *linePtr);
static TkTextSegment * FindTagStart(TkTextBTree tree, TkTextTag *tagPtr,
TkTextIndex *indexPtr);
static void AdjustStartEndRefs(BTree *treePtr, TkText *textPtr,
int action);
/*
* Actions for use by AdjustStartEndRefs
*/
#define TEXT_ADD_REFS 1
#define TEXT_REMOVE_REFS 2
/*
* Type record for character segments:
*/
const Tk_SegType tkTextCharType = {
"character", /* name */
0, /* leftGravity */
CharSplitProc, /* splitProc */
CharDeleteProc, /* deleteProc */
CharCleanupProc, /* cleanupProc */
NULL, /* lineChangeProc */
TkTextCharLayoutProc, /* layoutProc */
CharCheckProc /* checkProc */
};
/*
* Type record for segments marking the beginning of a tagged range:
*/
const Tk_SegType tkTextToggleOnType = {
"toggleOn", /* name */
0, /* leftGravity */
NULL, /* splitProc */
ToggleDeleteProc, /* deleteProc */
ToggleCleanupProc, /* cleanupProc */
ToggleLineChangeProc, /* lineChangeProc */
NULL, /* layoutProc */
ToggleCheckProc /* checkProc */
};
/*
* Type record for segments marking the end of a tagged range:
*/
const Tk_SegType tkTextToggleOffType = {
"toggleOff", /* name */
1, /* leftGravity */
NULL, /* splitProc */
ToggleDeleteProc, /* deleteProc */
ToggleCleanupProc, /* cleanupProc */
ToggleLineChangeProc, /* lineChangeProc */
NULL, /* layoutProc */
ToggleCheckProc /* checkProc */
};
/*
*----------------------------------------------------------------------
*
* TkBTreeCreate --
*
* This function 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(
TkSharedText *sharedTextPtr)
{
BTree *treePtr;
Node *rootPtr;
TkTextLine *linePtr, *linePtr2;
TkTextSegment *segPtr;
/*
* The tree will initially have two empty lines. The second line isn't
* actually part of the tree's contents, but its presence makes several
* operations easier. The tree will have one node, which is also the root
* of the tree.
*/
rootPtr = (Node *)ckalloc(sizeof(Node));
linePtr = (TkTextLine *)ckalloc(sizeof(TkTextLine));
linePtr2 = (TkTextLine *)ckalloc(sizeof(TkTextLine));
rootPtr->parentPtr = NULL;
rootPtr->nextPtr = NULL;
rootPtr->summaryPtr = NULL;
rootPtr->level = 0;
rootPtr->children.linePtr = linePtr;
rootPtr->numChildren = 2;
rootPtr->numLines = 2;
/*
* The tree currently has no registered clients, so all pixel count
* pointers are simply NULL.
*/
rootPtr->numPixels = NULL;
linePtr->pixels = NULL;
linePtr2->pixels = NULL;
linePtr->parentPtr = rootPtr;
linePtr->nextPtr = linePtr2;
segPtr = (TkTextSegment *)ckalloc(CSEG_SIZE(1));
linePtr->segPtr = segPtr;
segPtr->typePtr = &tkTextCharType;
segPtr->nextPtr = NULL;
segPtr->size = 1;
segPtr->body.chars[0] = '\n';
segPtr->body.chars[1] = 0;
linePtr2->parentPtr = rootPtr;
linePtr2->nextPtr = NULL;
segPtr = (TkTextSegment *)ckalloc(CSEG_SIZE(1));
linePtr2->segPtr = segPtr;
segPtr->typePtr = &tkTextCharType;
segPtr->nextPtr = NULL;
segPtr->size = 1;
segPtr->body.chars[0] = '\n';
segPtr->body.chars[1] = 0;
treePtr = (BTree *)ckalloc(sizeof(BTree));
treePtr->sharedTextPtr = sharedTextPtr;
treePtr->rootPtr = rootPtr;
treePtr->clients = 0;
treePtr->stateEpoch = 0;
treePtr->pixelReferences = 0;
treePtr->startEndCount = 0;
treePtr->startEnd = NULL;
treePtr->startEndRef = NULL;
return (TkTextBTree) treePtr;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeAddClient --
*
* This function is called to provide a client with access to a given
* B-tree. If the client wishes to make use of the B-tree's pixel height
* storage, caching and calculation mechanisms, then a non-negative
* 'defaultHeight' must be provided. In this case the return value is a
* pixel tree reference which must be provided in all of the B-tree API
* which refers to or modifies pixel heights:
*
* TkBTreeAdjustPixelHeight,
* TkBTreeFindPixelLine,
* TkBTreeNumPixels,
* TkBTreePixelsTo,
* (and two private functions AdjustPixelClient, RemovePixelClient).
*
* If this is not provided, then the above functions must never be called
* for this client.
*
* Results:
* The return value is the pixelReference used by the B-tree to refer to
* pixel counts for the new client. It should be stored by the caller. If
* defaultHeight was negative, then the return value will be -1.
*
* Side effects:
* Memory may be allocated and initialized.
*
*----------------------------------------------------------------------
*/
void
TkBTreeAddClient(
TkTextBTree tree, /* B-tree to add a client to. */
TkText *textPtr, /* Client to add. */
int defaultHeight) /* Default line height for the new client, or
* -1 if no pixel heights are to be kept. */
{
BTree *treePtr = (BTree *) tree;
if (treePtr == NULL) {
Tcl_Panic("NULL treePtr in TkBTreeAddClient");
}
if (textPtr->start != NULL || textPtr->end != NULL) {
AdjustStartEndRefs(treePtr, textPtr, TEXT_ADD_REFS);
}
if (defaultHeight >= 0) {
TkTextLine *end;
int counting = (textPtr->start == NULL ? 1 : 0);
int useReference = treePtr->pixelReferences;
/*
* We must set the 'end' value in AdjustPixelClient so that the last
* dummy line in the B-tree doesn't contain a pixel height.
*/
end = textPtr->end;
if (end == NULL) {
end = TkBTreeFindLine(tree, NULL, TkBTreeNumLines(tree, NULL));
}
AdjustPixelClient(treePtr, defaultHeight, treePtr->rootPtr,
textPtr->start, end, useReference, useReference+1, &counting);
textPtr->pixelReference = useReference;
treePtr->pixelReferences++;
} else {
textPtr->pixelReference = -1;
}
treePtr->clients++;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeClientRangeChanged --
*
* Called when the -startline or -endline options of a text widget client
* of the B-tree have changed.
*
* Results:
* None.
*
* Side effects:
* Lots of processing of the B-tree is done, with potential for memory to
* be allocated and initialized for the pixel heights of the widget.
*
*----------------------------------------------------------------------
*/
void
TkBTreeClientRangeChanged(
TkText *textPtr, /* Client whose start, end have changed. */
int defaultHeight) /* Default line height for the new client, or
* -1 if no pixel heights are to be kept. */
{
TkTextLine *end;
BTree *treePtr = (BTree *) textPtr->sharedTextPtr->tree;
int counting = (textPtr->start == NULL ? 1 : 0);
int useReference = textPtr->pixelReference;
AdjustStartEndRefs(treePtr, textPtr, TEXT_ADD_REFS | TEXT_REMOVE_REFS);
/*
* We must set the 'end' value in AdjustPixelClient so that the last dummy
* line in the B-tree doesn't contain a pixel height.
*/
end = textPtr->end;
if (end == NULL) {
end = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
NULL, TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL));
}
AdjustPixelClient(treePtr, defaultHeight, treePtr->rootPtr,
textPtr->start, end, useReference, treePtr->pixelReferences,
&counting);
}
/*
*----------------------------------------------------------------------
*
* TkBTreeDestroy --
*
* Delete a B-tree, recycling all of the storage it contains.
*
* Results:
* The tree is deleted, so 'tree' should never again be used.
*
* Side effects:
* Memory is freed.
*
*----------------------------------------------------------------------
*/
void
TkBTreeDestroy(
TkTextBTree tree) /* Tree to clean up. */
{
BTree *treePtr = (BTree *) tree;
/*
* There's no need to loop over each client of the tree, calling
* 'TkBTreeRemoveClient', since the 'DestroyNode' will clean everything up
* itself.
*/
DestroyNode(treePtr->rootPtr);
if (treePtr->startEnd != NULL) {
ckfree(treePtr->startEnd);
ckfree(treePtr->startEndRef);
}
ckfree(treePtr);
}
/*
*----------------------------------------------------------------------
*
* TkBTreeEpoch --
*
* Return the epoch for the B-tree. This number is incremented any time
* anything changes in the tree.
*
* Results:
* The epoch number.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkBTreeEpoch(
TkTextBTree tree) /* Tree to get epoch for. */
{
BTree *treePtr = (BTree *) tree;
return treePtr->stateEpoch;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeRemoveClient --
*
* Remove a client widget from its B-tree, cleaning up the pixel arrays
* which it uses if necessary. If this is the last such widget, we also
* destroy the whole tree.
*
* Results:
* All tree-specific aspects of the given client are deleted. If no more
* references exist, then the given tree is also deleted (in which case
* 'tree' must not be used again).
*
* Side effects:
* Memory may be freed.
*
*----------------------------------------------------------------------
*/
void
TkBTreeRemoveClient(
TkTextBTree tree, /* Tree to remove client from. */
TkText *textPtr) /* Client to remove. */
{
BTree *treePtr = (BTree *) tree;
int pixelReference = textPtr->pixelReference;
if (treePtr->clients == 1) {
/*
* The last reference to the tree.
*/
DestroyNode(treePtr->rootPtr);
ckfree(treePtr);
return;
} else if (pixelReference == -1) {
/*
* A client which doesn't care about pixels.
*/
treePtr->clients--;
} else {
/*
* Clean up pixel data for the given reference.
*/
if (pixelReference == (treePtr->pixelReferences-1)) {
/*
* The widget we're removing has the last index, so deletion is
* easier.
*/
RemovePixelClient(treePtr, treePtr->rootPtr, -1);
} else {
TkText *adjustPtr;
RemovePixelClient(treePtr, treePtr->rootPtr, pixelReference);
/*
* Now we need to adjust the 'pixelReference' of the peer widget
* whose storage we've just moved.
*/
adjustPtr = treePtr->sharedTextPtr->peers;
while (adjustPtr != NULL) {
if (adjustPtr->pixelReference == treePtr->pixelReferences-1) {
adjustPtr->pixelReference = pixelReference;
break;
}
adjustPtr = adjustPtr->next;
}
if (adjustPtr == NULL) {
Tcl_Panic("Couldn't find text widget with correct reference");
}
}
treePtr->pixelReferences--;
treePtr->clients--;
}
if (textPtr->start != NULL || textPtr->end != NULL) {
AdjustStartEndRefs(treePtr, textPtr, TEXT_REMOVE_REFS);
}
}
/*
*----------------------------------------------------------------------
*
* AdjustStartEndRefs --
*
* Modify B-tree's cache of start, end lines for the given text widget.
*
* Results:
* None.
*
* Side effects:
* The number of cached items may change (treePtr->startEndCount).
*
*----------------------------------------------------------------------
*/
static void
AdjustStartEndRefs(
BTree *treePtr, /* The entire B-tree. */
TkText *textPtr, /* The text widget for which we want to adjust
* it's start and end cache. */
int action) /* Action to perform. */
{
if (action & TEXT_REMOVE_REFS) {
int i = 0;
int count = 0;
while (i < treePtr->startEndCount) {
if (i != count) {
treePtr->startEnd[count] = treePtr->startEnd[i];
treePtr->startEndRef[count] = treePtr->startEndRef[i];
}
if (treePtr->startEndRef[i] != textPtr) {
count++;
}
i++;
}
treePtr->startEndCount = count;
treePtr->startEnd = (TkTextLine **)ckrealloc(treePtr->startEnd,
sizeof(TkTextLine *) * count);
treePtr->startEndRef = (TkText **)ckrealloc(treePtr->startEndRef,
sizeof(TkText *) * count);
}
if ((action & TEXT_ADD_REFS)
&& (textPtr->start != NULL || textPtr->end != NULL)) {
int count;
if (textPtr->start != NULL) {
treePtr->startEndCount++;
}
if (textPtr->end != NULL) {
treePtr->startEndCount++;
}
count = treePtr->startEndCount;
treePtr->startEnd = (TkTextLine **)ckrealloc(treePtr->startEnd,
sizeof(TkTextLine *) * count);
treePtr->startEndRef = (TkText **)ckrealloc(treePtr->startEndRef,
sizeof(TkText *) * count);
if (textPtr->start != NULL) {
count--;
treePtr->startEnd[count] = textPtr->start;
treePtr->startEndRef[count] = textPtr;
}
if (textPtr->end != NULL) {
count--;
treePtr->startEnd[count] = textPtr->end;
treePtr->startEndRef[count] = textPtr;
}
}
}
/*
*----------------------------------------------------------------------
*
* AdjustPixelClient --
*
* Utility function used to update all data structures for the existence
* of a new peer widget based on this B-tree, or for the modification of
* the start, end lines of an existing peer widget.
*
* Immediately _after_ calling this, treePtr->pixelReferences and
* treePtr->clients should be adjusted if needed (i.e. if this is a new
* peer).
*
* Results:
* None.
*
* Side effects:
* All the storage for Nodes and TkTextLines in the tree may be adjusted.
*
*----------------------------------------------------------------------
*/
static int
AdjustPixelClient(
BTree *treePtr, /* Pointer to tree. */
int defaultHeight, /* Default pixel line height, which can be
* zero. */
Node *nodePtr, /* Adjust from this node downwards. */
TkTextLine *start, /* First line for this pixel client. */
TkTextLine *end, /* Last line for this pixel client. */
int useReference, /* pixel reference for the client we are
* adding or changing. */
int newPixelReferences, /* New number of pixel references to this
* B-tree. */
int *counting) /* References an integer which is zero if
* we're outside the relevant range for this
* client, and 1 if we're inside. */
{
int pixelCount = 0;
/*
* Traverse entire tree down from nodePtr, reallocating pixel structures
* for each Node and TkTextLine, adding room for the new peer's pixel
* information (1 extra int per Node, 2 extra ints per TkTextLine). Also
* copy the information from the last peer into the new space (so it
* contains something sensible).
*/
if (nodePtr->level != 0) {
Node *loopPtr = nodePtr->children.nodePtr;
while (loopPtr != NULL) {
pixelCount += AdjustPixelClient(treePtr, defaultHeight, loopPtr,
start, end, useReference, newPixelReferences, counting);
loopPtr = loopPtr->nextPtr;
}
} else {
TkTextLine *linePtr = nodePtr->children.linePtr;
while (linePtr != NULL) {
if (!*counting && (linePtr == start)) {
*counting = 1;
}
if (*counting && (linePtr == end)) {
*counting = 0;
}
if (newPixelReferences != treePtr->pixelReferences) {
linePtr->pixels = (int *)ckrealloc(linePtr->pixels,
sizeof(int) * 2 * newPixelReferences);
}
/*
* Notice that for the very last line, we are never counting and
* therefore this always has a height of 0 and an epoch of 1.
*/
linePtr->pixels[2*useReference] = (*counting ? defaultHeight : 0);
linePtr->pixels[2*useReference+1] = (*counting ? 0 : 1);
pixelCount += linePtr->pixels[2*useReference];
linePtr = linePtr->nextPtr;
}
}
if (newPixelReferences != treePtr->pixelReferences) {
nodePtr->numPixels = (int *)ckrealloc(nodePtr->numPixels,
sizeof(int) * newPixelReferences);
}
nodePtr->numPixels[useReference] = pixelCount;
return pixelCount;
}
/*
*----------------------------------------------------------------------
*
* RemovePixelClient --
*
* Utility function used to update all data structures for the removal of
* a peer widget which used to be based on this B-tree.
*
* Immediately _after_ calling this, treePtr->clients should be
* decremented.
*
* Results:
* None.
*
* Side effects:
* All the storage for Nodes and TkTextLines in the tree may be adjusted.
*
*----------------------------------------------------------------------
*/
static void
RemovePixelClient(
BTree *treePtr, /* Pointer to tree. */
Node *nodePtr, /* Adjust from this node downwards. */
int overwriteWithLast) /* Over-write this peer widget's information
* with the last one. */
{
/*
* Traverse entire tree down from nodePtr, reallocating pixel structures
* for each Node and TkTextLine, removing space allocated for one peer. If
* 'overwriteWithLast' is not -1, then copy the information which was in
* the last slot on top of one of the others (i.e. it's not the last one
* we're deleting).
*/
if (overwriteWithLast != -1) {
nodePtr->numPixels[overwriteWithLast] =
nodePtr->numPixels[treePtr->pixelReferences-1];
}
if (treePtr->pixelReferences == 1) {
ckfree(nodePtr->numPixels);
nodePtr->numPixels = NULL;
} else {
nodePtr->numPixels = (int *)ckrealloc(nodePtr->numPixels,
sizeof(int) * (treePtr->pixelReferences - 1));
}
if (nodePtr->level != 0) {
nodePtr = nodePtr->children.nodePtr;
while (nodePtr != NULL) {
RemovePixelClient(treePtr, nodePtr, overwriteWithLast);
nodePtr = nodePtr->nextPtr;
}
} else {
TkTextLine *linePtr = nodePtr->children.linePtr;
while (linePtr != NULL) {
if (overwriteWithLast != -1) {
linePtr->pixels[2*overwriteWithLast] =
linePtr->pixels[2*(treePtr->pixelReferences-1)];
linePtr->pixels[1+2*overwriteWithLast] =
linePtr->pixels[1+2*(treePtr->pixelReferences-1)];
}
if (treePtr->pixelReferences == 1) {
linePtr->pixels = NULL;
} else {
linePtr->pixels = (int *)ckrealloc(linePtr->pixels,
sizeof(int) * 2 * (treePtr->pixelReferences-1));
}
linePtr = linePtr->nextPtr;
}
}
}
/*
*----------------------------------------------------------------------
*
* DestroyNode --
*
* This is a recursive utility function 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(
Node *nodePtr) /* Destroy from this node downwards. */
{
if (nodePtr->level == 0) {
TkTextLine *linePtr;
TkTextSegment *segPtr;
while (nodePtr->children.linePtr != NULL) {
linePtr = nodePtr->children.linePtr;
nodePtr->children.linePtr = linePtr->nextPtr;
while (linePtr->segPtr != NULL) {
segPtr = linePtr->segPtr;
linePtr->segPtr = segPtr->nextPtr;
segPtr->typePtr->deleteProc(segPtr, linePtr, 1);
}
ckfree(linePtr->pixels);
ckfree(linePtr);
}
} else {
Node *childPtr;
while (nodePtr->children.nodePtr != NULL) {
childPtr = nodePtr->children.nodePtr;
nodePtr->children.nodePtr = childPtr->nextPtr;
DestroyNode(childPtr);
}
}
DeleteSummaries(nodePtr->summaryPtr);
ckfree(nodePtr->numPixels);
ckfree(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(
Summary *summaryPtr)
/* First in list of node's tag summaries. */
{
Summary *nextPtr;
while (summaryPtr != NULL) {
nextPtr = summaryPtr->nextPtr;
ckfree(summaryPtr);
summaryPtr = nextPtr;
}
}
/*
*----------------------------------------------------------------------
*
* TkBTreeAdjustPixelHeight --
*
* Adjust the pixel height of a given logical line to the specified
* value.
*
* Results:
* Total number of valid pixels currently known in the tree.
*
* Side effects:
* Updates overall data structures so pixel height count is consistent.
*
*----------------------------------------------------------------------
*/
int
TkBTreeAdjustPixelHeight(
const TkText *textPtr, /* Client of the B-tree. */
TkTextLine *linePtr,
/* The logical line to update. */
int newPixelHeight, /* The line's known height in pixels. */
int mergedLogicalLines) /* The number of extra logical lines which
* have been merged with this one (due to
* elided eols). They will have their pixel
* height set to zero, and the total pixel
* height associated with the given
* linePtr. */
{
Node *nodePtr;
int changeToPixelCount; /* Counts change to total number of pixels in
* file. */
int pixelReference = textPtr->pixelReference;
changeToPixelCount = newPixelHeight - linePtr->pixels[2 * pixelReference];
/*
* Increment the pixel counts in all the parent nodes of the current line,
* then rebalance the tree if necessary.
*/
nodePtr = linePtr->parentPtr;
nodePtr->numPixels[pixelReference] += changeToPixelCount;
while (nodePtr->parentPtr != NULL) {
nodePtr = nodePtr->parentPtr;
nodePtr->numPixels[pixelReference] += changeToPixelCount;
}
linePtr->pixels[2 * pixelReference] = newPixelHeight;
/*
* Any merged logical lines must have their height set to zero.
*/
while (mergedLogicalLines-- > 0) {
linePtr = TkBTreeNextLine(textPtr, linePtr);
TkBTreeAdjustPixelHeight(textPtr, linePtr, 0, 0);
}
/*
* Return total number of pixels in the tree.
*/
return nodePtr->numPixels[pixelReference];
}
/*
*----------------------------------------------------------------------
*
* TkBTreeInsertChars --
*
* Insert characters at a given position in a B-tree.
*
* Results:
* None.
*
* Side effects:
* Characters are added to the B-tree at the given position. If the
* string contains newlines, new lines will be added, which could cause
* the structure of the B-tree to change.
*
*----------------------------------------------------------------------
*/
void
TkBTreeInsertChars(
TkTextBTree tree, /* Tree to insert into. */
TkTextIndex *indexPtr,
/* Indicates where to insert text. When the
* function returns, this index is no longer
* valid because of changes to the segment
* structure. */
const char *string) /* Pointer to bytes to insert (may contain
* newlines, must be null-terminated). */
{
Node *nodePtr;
TkTextSegment *prevPtr;
/* The segment just before the first new
* segment (NULL means new segment is at
* beginning of line). */
TkTextSegment *curPtr; /* Current segment; new characters are
* inserted just after this one. NULL means
* insert at beginning of line. */
TkTextLine *linePtr; /* Current line (new segments are added to
* this line). */
TkTextSegment *segPtr;
TkTextLine *newLinePtr;
int chunkSize; /* # characters in current chunk. */
const char *eol; /* Pointer to character just after last one in
* current chunk. */
int changeToLineCount; /* Counts change to total number of lines in
* file. */
int *changeToPixelCount; /* Counts change to total number of pixels in
* file. */
int ref;
int pixels[PIXEL_CLIENTS];
BTree *treePtr = (BTree *) tree;
treePtr->stateEpoch++;
prevPtr = SplitSeg(indexPtr);
linePtr = indexPtr->linePtr;
curPtr = prevPtr;
/*
* Chop the string up into lines and create a new segment for each line,
* plus a new line for the leftovers from the previous line.
*/
changeToLineCount = 0;
if (treePtr->pixelReferences > PIXEL_CLIENTS) {
changeToPixelCount = (int *)ckalloc(sizeof(int) * treePtr->pixelReferences);
} else {
changeToPixelCount = pixels;
}
for (ref = 0; ref < treePtr->pixelReferences; ref++) {
changeToPixelCount[ref] = 0;
}
while (*string != 0) {
for (eol = string; *eol != 0; eol++) {
if (*eol == '\n') {
eol++;
break;
}
}
chunkSize = eol-string;
segPtr = (TkTextSegment *)ckalloc(CSEG_SIZE(chunkSize));
segPtr->typePtr = &tkTextCharType;
if (curPtr == NULL) {
segPtr->nextPtr = linePtr->segPtr;
linePtr->segPtr = segPtr;
} else {
segPtr->nextPtr = curPtr->nextPtr;
curPtr->nextPtr = segPtr;
}
segPtr->size = chunkSize;
memcpy(segPtr->body.chars, string, chunkSize);
segPtr->body.chars[chunkSize] = 0;
if (eol[-1] != '\n') {
break;
}
/*
* The chunk ended with a newline, so create a new TkTextLine and move
* the remainder of the old line to it.
*/
newLinePtr = (TkTextLine *)ckalloc(sizeof(TkTextLine));
newLinePtr->pixels = (int *)
ckalloc(sizeof(int) * 2 * treePtr->pixelReferences);
newLinePtr->parentPtr = linePtr->parentPtr;
newLinePtr->nextPtr = linePtr->nextPtr;
linePtr->nextPtr = newLinePtr;
newLinePtr->segPtr = segPtr->nextPtr;
/*
* Set up a starting default height, which will be re-adjusted later.
* We need to do this for each referenced widget.
*/
for (ref = 0; ref < treePtr->pixelReferences; ref++) {
newLinePtr->pixels[2 * ref] = linePtr->pixels[2 * ref];
newLinePtr->pixels[2 * ref + 1] = 0;
changeToPixelCount[ref] += newLinePtr->pixels[2 * ref];
}
segPtr->nextPtr = NULL;
linePtr = newLinePtr;
curPtr = NULL;
changeToLineCount++;
string = eol;
}
/*
* I don't believe it's possible for either of the two lines passed to
* this function to be the last line of text, but the function is robust
* to that case anyway. (We must never re-calculate the line height of
* the last line).
*/
TkTextInvalidateLineMetrics(treePtr->sharedTextPtr, NULL,
indexPtr->linePtr, changeToLineCount, TK_TEXT_INVALIDATE_INSERT);
/*
* Cleanup the starting line for the insertion, plus the ending line if
* it's different.
*/
CleanupLine(indexPtr->linePtr);
if (linePtr != indexPtr->linePtr) {
CleanupLine(linePtr);
}
/*
* Increment the line and pixel counts in all the parent nodes of the
* insertion point, then rebalance the tree if necessary.
*/
for (nodePtr = linePtr->parentPtr ; nodePtr != NULL;
nodePtr = nodePtr->parentPtr) {
nodePtr->numLines += changeToLineCount;
for (ref = 0; ref < treePtr->pixelReferences; ref++) {
nodePtr->numPixels[ref] += changeToPixelCount[ref];
}
}
if (treePtr->pixelReferences > PIXEL_CLIENTS) {
ckfree(changeToPixelCount);
}
nodePtr = linePtr->parentPtr;
nodePtr->numChildren += changeToLineCount;
if (nodePtr->numChildren > MAX_CHILDREN) {
Rebalance(treePtr, nodePtr);
}
if (tkBTreeDebug) {
TkBTreeCheck(indexPtr->tree);
}
}
/*
*--------------------------------------------------------------
*
* SplitSeg --
*
* This function is called before adding or deleting segments. It does
* three things: (a) it finds the segment containing indexPtr; (b) if
* there are several such segments (because some segments have zero
* length) then it picks the first segment that does not have left
* gravity; (c) if the index refers to the middle of a segment then it
* splits the segment so that the index now refers to the beginning of a
* segment.
*
* Results:
* The return value is a pointer to the segment just before the segment
* corresponding to indexPtr (as described above). If the segment
* corresponding to indexPtr is the first in its line then the return
* value is NULL.
*
* Side effects:
* The segment referred to by indexPtr is split unless indexPtr refers to
* its first character.
*
*--------------------------------------------------------------
*/
static TkTextSegment *
SplitSeg(
TkTextIndex *indexPtr) /* Index identifying position at which to
* split a segment. */
{
TkTextSegment *prevPtr, *segPtr;
TkTextLine *linePtr;
int count = indexPtr->byteIndex;
linePtr = indexPtr->linePtr;
prevPtr = NULL;
segPtr = linePtr->segPtr;
while (segPtr != NULL) {
if (segPtr->size > count) {
if (count == 0) {
return prevPtr;
}
segPtr = segPtr->typePtr->splitProc(segPtr, count);
if (prevPtr == NULL) {
indexPtr->linePtr->segPtr = segPtr;
} else {
prevPtr->nextPtr = segPtr;
}
return segPtr;
} else if ((segPtr->size == 0) && (count == 0)
&& !segPtr->typePtr->leftGravity) {
return prevPtr;
}
count -= segPtr->size;
prevPtr = segPtr;
segPtr = segPtr->nextPtr;
if (segPtr == NULL) {
/*
* Two logical lines merged into one display line through eliding
* of a newline.
*/
linePtr = TkBTreeNextLine(NULL, linePtr);
if (linePtr == NULL) {
/*
* Reached end of the text.
*/
} else {
segPtr = linePtr->segPtr;
}
}
}
Tcl_Panic("SplitSeg reached end of line!");
return NULL;
}
/*
*--------------------------------------------------------------
*
* CleanupLine --
*
* This function is called after modifications have been made to a line.
* It scans over all of the segments in the line, giving each a chance to
* clean itself up, e.g. by merging with the following segments, updating
* internal information, etc.
*
* Results:
* None.
*
* Side effects:
* Depends on what the segment-specific cleanup functions do.
*
*--------------------------------------------------------------
*/
static void
CleanupLine(
TkTextLine *linePtr) /* Line to be cleaned up. */
{
TkTextSegment *segPtr, **prevPtrPtr;
int anyChanges;
/*
* Make a pass over all of the segments in the line, giving each a chance
* to clean itself up. This could potentially change the structure of the
* line, e.g. by merging two segments together or having two segments
* cancel themselves; if so, then repeat the whole process again, since
* the first structure change might make other structure changes possible.
* Repeat until eventually there are no changes.
*/
while (1) {
anyChanges = 0;
for (prevPtrPtr = &linePtr->segPtr, segPtr = *prevPtrPtr;
segPtr != NULL;
prevPtrPtr = &(*prevPtrPtr)->nextPtr, segPtr = *prevPtrPtr) {
if (segPtr->typePtr->cleanupProc != NULL) {
*prevPtrPtr = segPtr->typePtr->cleanupProc(segPtr, linePtr);
if (segPtr != *prevPtrPtr) {
anyChanges = 1;
}
}
}
if (!anyChanges) {
break;
}
}
}
/*
*----------------------------------------------------------------------
*
* TkBTreeDeleteIndexRange --
*
* Delete a range of characters from a B-tree. The caller must make sure
* that the final newline of the B-tree is never deleted.
*
* Results:
* None.
*
* Side effects:
* Information is deleted from the B-tree. This can cause the internal
* structure of the B-tree to change. Note: because of changes to the
* B-tree structure, the indices pointed to by index1Ptr and index2Ptr
* should not be used after this function returns.
*
*----------------------------------------------------------------------
*/
void
TkBTreeDeleteIndexRange(
TkTextBTree tree, /* Tree to delete from. */
TkTextIndex *index1Ptr,
/* Indicates first character that is to be
* deleted. */
TkTextIndex *index2Ptr)
/* Indicates character just after the last one
* that is to be deleted. */
{
TkTextSegment *prevPtr; /* The segment just before the start of the
* deletion range. */
TkTextSegment *lastPtr; /* The segment just after the end of the
* deletion range. */
TkTextSegment *segPtr, *nextPtr;
TkTextLine *curLinePtr;
Node *curNodePtr, *nodePtr;
int changeToLineCount = 0;
int ref;
BTree *treePtr = (BTree *) tree;
treePtr->stateEpoch++;
/*
* Tricky point: split at index2Ptr first; otherwise the split at
* index2Ptr may invalidate segPtr and/or prevPtr.
*/
lastPtr = SplitSeg(index2Ptr);
if (lastPtr != NULL) {
lastPtr = lastPtr->nextPtr;
} else {
lastPtr = index2Ptr->linePtr->segPtr;
}
prevPtr = SplitSeg(index1Ptr);
if (prevPtr != NULL) {
segPtr = prevPtr->nextPtr;
prevPtr->nextPtr = lastPtr;
} else {
segPtr = index1Ptr->linePtr->segPtr;
index1Ptr->linePtr->segPtr = lastPtr;
}
/*
* Delete all of the segments between prevPtr and lastPtr.
*/
curLinePtr = index1Ptr->linePtr;
curNodePtr = curLinePtr->parentPtr;
while (segPtr != lastPtr) {
if (segPtr == NULL) {
TkTextLine *nextLinePtr;
/*
* We just ran off the end of a line. First find the next line,
* then go back to the old line and delete it (unless it's the
* starting line for the range).
*/
nextLinePtr = TkBTreeNextLine(NULL, curLinePtr);
if (curLinePtr != index1Ptr->linePtr) {
if (curNodePtr == index1Ptr->linePtr->parentPtr) {
index1Ptr->linePtr->nextPtr = curLinePtr->nextPtr;
} else {
curNodePtr->children.linePtr = curLinePtr->nextPtr;
}
for (nodePtr = curNodePtr; nodePtr != NULL;
nodePtr = nodePtr->parentPtr) {
nodePtr->numLines--;
for (ref = 0; ref < treePtr->pixelReferences; ref++) {
nodePtr->numPixels[ref] -= curLinePtr->pixels[2*ref];
}
}
changeToLineCount++;
CLANG_ASSERT(curNodePtr);
curNodePtr->numChildren--;
/*
* Check if we need to adjust any partial clients.
*/
if (treePtr->startEnd != NULL) {
int checkCount = 0;
while (checkCount < treePtr->startEndCount) {
if (treePtr->startEnd[checkCount] == curLinePtr) {
TkText *peer = treePtr->startEndRef[checkCount];
/*
* We're deleting a line which is the start or end
* of a current client. This means we need to
* adjust that client.
*/
treePtr->startEnd[checkCount] = nextLinePtr;
if (peer->start == curLinePtr) {
peer->start = nextLinePtr;
}
if (peer->end == curLinePtr) {
peer->end = nextLinePtr;
}
}
checkCount++;
}
}
ckfree(curLinePtr->pixels);
ckfree(curLinePtr);
}
curLinePtr = nextLinePtr;
segPtr = curLinePtr->segPtr;
/*
* If the node is empty then delete it and its parents recursively
* upwards until a non-empty node is found.
*/
while (curNodePtr->numChildren == 0) {
Node *parentPtr;
parentPtr = curNodePtr->parentPtr;
if (parentPtr->children.nodePtr == curNodePtr) {
parentPtr->children.nodePtr = curNodePtr->nextPtr;
} else {
Node *prevNodePtr = parentPtr->children.nodePtr;
while (prevNodePtr->nextPtr != curNodePtr) {
prevNodePtr = prevNodePtr->nextPtr;
}
prevNodePtr->nextPtr = curNodePtr->nextPtr;
}
parentPtr->numChildren--;
DeleteSummaries(curNodePtr->summaryPtr);
ckfree(curNodePtr->numPixels);
ckfree(curNodePtr);
curNodePtr = parentPtr;
}
curNodePtr = curLinePtr->parentPtr;
continue;
}
nextPtr = segPtr->nextPtr;
if (segPtr->typePtr->deleteProc(segPtr, curLinePtr, 0) != 0) {
/*
* This segment refuses to die. Move it to prevPtr and advance
* prevPtr if the segment has left gravity.
*/
if (prevPtr == NULL) {
segPtr->nextPtr = index1Ptr->linePtr->segPtr;
index1Ptr->linePtr->segPtr = segPtr;
} else {
segPtr->nextPtr = prevPtr->nextPtr;
prevPtr->nextPtr = segPtr;
}
if (segPtr->typePtr->leftGravity) {
prevPtr = segPtr;
}
}
segPtr = nextPtr;
}
/*
* If the beginning and end of the deletion range are in different lines,
* join the two lines together and discard the ending line.
*/
if (index1Ptr->linePtr != index2Ptr->linePtr) {
TkTextLine *prevLinePtr;
for (segPtr = lastPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
if (segPtr->typePtr->lineChangeProc != NULL) {
segPtr->typePtr->lineChangeProc(segPtr, index2Ptr->linePtr);
}
}
curNodePtr = index2Ptr->linePtr->parentPtr;
for (nodePtr = curNodePtr; nodePtr != NULL;
nodePtr = nodePtr->parentPtr) {
nodePtr->numLines--;
for (ref = 0; ref < treePtr->pixelReferences; ref++) {
nodePtr->numPixels[ref] -= index2Ptr->linePtr->pixels[2*ref];
}
}
changeToLineCount++;
curNodePtr->numChildren--;
prevLinePtr = curNodePtr->children.linePtr;
if (prevLinePtr == index2Ptr->linePtr) {
curNodePtr->children.linePtr = index2Ptr->linePtr->nextPtr;
} else {
while (prevLinePtr->nextPtr != index2Ptr->linePtr) {
prevLinePtr = prevLinePtr->nextPtr;
}
prevLinePtr->nextPtr = index2Ptr->linePtr->nextPtr;
}
/*
* Check if we need to adjust any partial clients. In this case if
* we're deleting the line, we actually move back to the previous line
* for our (start,end) storage. We do this because we still want the
* portion of the second line that still exists to be in the start,end
* range.
*/
if (treePtr->startEnd != NULL) {
int checkCount = 0;
while (checkCount < treePtr->startEndCount &&
treePtr->startEnd[checkCount] != NULL) {
if (treePtr->startEnd[checkCount] == index2Ptr->linePtr) {
TkText *peer = treePtr->startEndRef[checkCount];
/*
* We're deleting a line which is the start or end of a
* current client. This means we need to adjust that
* client.
*/
treePtr->startEnd[checkCount] = index1Ptr->linePtr;
if (peer->start == index2Ptr->linePtr) {
peer->start = index1Ptr->linePtr;
}
if (peer->end == index2Ptr->linePtr) {
peer->end = index1Ptr->linePtr;
}
}
checkCount++;
}
}
ckfree(index2Ptr->linePtr->pixels);
ckfree(index2Ptr->linePtr);
Rebalance((BTree *) index2Ptr->tree, curNodePtr);
}
/*
* Cleanup the segments in the new line.
*/
CleanupLine(index1Ptr->linePtr);
/*
* This line now needs to have its height recalculated. For safety, ensure
* we don't call this function with the last artificial line of text. I
* _believe_ that it isn't possible to get this far with the last line,
* but it is good to be safe.
*/
if (TkBTreeNextLine(NULL, index1Ptr->linePtr) != NULL) {
TkTextInvalidateLineMetrics(treePtr->sharedTextPtr, NULL,
index1Ptr->linePtr, changeToLineCount,
TK_TEXT_INVALIDATE_DELETE);
}
/*
* Lastly, rebalance the first node of the range.
*/
Rebalance((BTree *) index1Ptr->tree, index1Ptr->linePtr->parentPtr);
if (tkBTreeDebug) {
TkBTreeCheck(index1Ptr->tree);
}
}
/*
*----------------------------------------------------------------------
*
* TkBTreeFindLine --
*
* Find a particular line in a B-tree based on its line number.
*
* Results:
* The return value is a pointer to the line structure for the line whose
* index is "line", or NULL if no such line exists.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
TkTextLine *
TkBTreeFindLine(
TkTextBTree tree, /* B-tree in which to find line. */
const TkText *textPtr, /* Relative to this client of the B-tree. */
int line) /* Index of desired line. */
{
BTree *treePtr = (BTree *) tree;
Node *nodePtr;
TkTextLine *linePtr;
if (treePtr == NULL) {
treePtr = (BTree *) textPtr->sharedTextPtr->tree;
}
nodePtr = treePtr->rootPtr;
if ((line < 0) || (line >= nodePtr->numLines)) {
return NULL;
}
/*
* Check for any start/end offset for this text widget.
*/
if (textPtr != NULL) {
if (textPtr->start != NULL) {
line += TkBTreeLinesTo(NULL, textPtr->start);
if (line >= nodePtr->numLines) {
return NULL;
}
}
if (textPtr->end != NULL) {
if (line > TkBTreeLinesTo(NULL, textPtr->end)) {
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 <= line;
nodePtr = nodePtr->nextPtr) {
if (nodePtr == NULL) {
Tcl_Panic("TkBTreeFindLine ran out of nodes");
}
line -= nodePtr->numLines;
}
}
/*
* Work through the lines attached to the level-0 node.
*/
for (linePtr = nodePtr->children.linePtr; line > 0;
linePtr = linePtr->nextPtr) {
if (linePtr == NULL) {
Tcl_Panic("TkBTreeFindLine ran out of lines");
}
line -= 1;
}
return linePtr;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeFindPixelLine --
*
* Find a particular line in a B-tree based on its pixel count.
*
* Results:
* The return value is a pointer to the line structure for the line which
* contains the pixel "pixels", or NULL if no such line exists. If the
* first line is of height 20, then pixels 0-19 will return it, and
* pixels = 20 will return the next line.
*
* If pixelOffset is non-NULL, it is set to the amount by which 'pixels'
* exceeds the first pixel located on the returned line. This should
* always be non-negative.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
TkTextLine *
TkBTreeFindPixelLine(
TkTextBTree tree, /* B-tree to use. */
const TkText *textPtr, /* Relative to this client of the B-tree. */
int pixels, /* Pixel index of desired line. */
int *pixelOffset) /* Used to return offset. */
{
BTree *treePtr = (BTree *) tree;
Node *nodePtr;
TkTextLine *linePtr;
int pixelReference = textPtr->pixelReference;
nodePtr = treePtr->rootPtr;
if ((pixels < 0) || (pixels > nodePtr->numPixels[pixelReference])) {
return NULL;
}
if (nodePtr->numPixels[pixelReference] == 0) {
Tcl_Panic("TkBTreeFindPixelLine called with empty window");
}
/*
* 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->numPixels[pixelReference] <= pixels;
nodePtr = nodePtr->nextPtr) {
if (nodePtr == NULL) {
Tcl_Panic("TkBTreeFindPixelLine ran out of nodes");
}
pixels -= nodePtr->numPixels[pixelReference];
}
}
/*
* Work through the lines attached to the level-0 node.
*/
for (linePtr = nodePtr->children.linePtr;
linePtr->pixels[2 * pixelReference] < pixels;
linePtr = linePtr->nextPtr) {
if (linePtr == NULL) {
Tcl_Panic("TkBTreeFindPixelLine ran out of lines");
}
pixels -= linePtr->pixels[2 * pixelReference];
}
/*
* Check for any start/end offset for this text widget.
*/
if (textPtr->start != NULL) {
int lineBoundary = TkBTreeLinesTo(NULL, textPtr->start);
if (TkBTreeLinesTo(NULL, linePtr) < lineBoundary) {
linePtr = TkBTreeFindLine(tree, NULL, lineBoundary);
}
}
if (textPtr->end != NULL) {
int lineBoundary = TkBTreeLinesTo(NULL, textPtr->end);
if (TkBTreeLinesTo(NULL, linePtr) > lineBoundary) {
linePtr = TkBTreeFindLine(tree, NULL, lineBoundary);
}
}
if (pixelOffset != NULL && linePtr != NULL) {
*pixelOffset = pixels;
}
return linePtr;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeNextLine --
*
* Given an existing line in a B-tree, this function locates the next
* line in the B-tree. This function 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(
const TkText *textPtr, /* Next line in the context of this client. */
TkTextLine *linePtr)
/* Pointer to existing line in B-tree. */
{
Node *nodePtr;
if (linePtr->nextPtr != NULL) {
if (textPtr != NULL && (linePtr == textPtr->end)) {
return NULL;
} else {
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 NULL;
}
}
while (nodePtr->level > 0) {
nodePtr = nodePtr->children.nodePtr;
}
return nodePtr->children.linePtr;
}
/*
*----------------------------------------------------------------------
*
* TkBTreePreviousLine --
*
* Given an existing line in a B-tree, this function locates the previous
* line in the B-tree. This function is used for scanning through the
* B-tree in the reverse direction.
*
* Results:
* The return value is a pointer to the line that immediately preceeds
* linePtr, or NULL if there is no such line.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
TkTextLine *
TkBTreePreviousLine(
TkText *textPtr, /* Relative to this client of the B-tree. */
TkTextLine *linePtr)
/* Pointer to existing line in B-tree. */
{
Node *nodePtr;
Node *node2Ptr;
TkTextLine *prevPtr;
if (textPtr != NULL && textPtr->start == linePtr) {
return NULL;
}
/*
* Find the line under this node just before the starting line.
*/
prevPtr = linePtr->parentPtr->children.linePtr; /* First line at leaf. */
while (prevPtr != linePtr) {
if (prevPtr->nextPtr == linePtr) {
return prevPtr;
}
prevPtr = prevPtr->nextPtr;
if (prevPtr == NULL) {
Tcl_Panic("TkBTreePreviousLine ran out of lines");
}
}
/*
* This was the first line associated with the particular parent node.
* Search up the tree for the previous node, then search down from that
* node to find its last line.
*/
for (nodePtr = linePtr->parentPtr; ; nodePtr = nodePtr->parentPtr) {
if (nodePtr == NULL || nodePtr->parentPtr == NULL) {
return NULL;
}
if (nodePtr != nodePtr->parentPtr->children.nodePtr) {
break;
}
}
for (node2Ptr = nodePtr->parentPtr->children.nodePtr; ;
node2Ptr = node2Ptr->children.nodePtr) {
while (node2Ptr->nextPtr != nodePtr) {
node2Ptr = node2Ptr->nextPtr;
}
if (node2Ptr->level == 0) {
break;
}
nodePtr = NULL;
}
for (prevPtr = node2Ptr->children.linePtr ; ; prevPtr = prevPtr->nextPtr) {
if (prevPtr->nextPtr == NULL) {
return prevPtr;
}
}
}
/*
*----------------------------------------------------------------------
*
* TkBTreePixelsTo --
*
* Given a pointer to a line in a B-tree, return the numerical pixel
* index of the top of that line (i.e. the result does not include the
* height of the given line).
*
* Since the last line of text (the artificial one) has zero height by
* defintion, calling this with the last line will return the total
* number of pixels in the widget.
*
* Results:
* The result is the pixel height of the top of the given line.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkBTreePixelsTo(
const TkText *textPtr, /* Relative to this client of the B-tree. */
TkTextLine *linePtr) /* Pointer to existing line in B-tree. */
{
TkTextLine *linePtr2;
Node *nodePtr, *parentPtr;
int index;
int pixelReference = textPtr->pixelReference;
/*
* First count how many pixels precede this line in its level-0 node.
*/
nodePtr = linePtr->parentPtr;
index = 0;
for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr;
linePtr2 = linePtr2->nextPtr) {
if (linePtr2 == NULL) {
Tcl_Panic("TkBTreePixelsTo couldn't find line");
}
index += linePtr2->pixels[2 * pixelReference];
}
/*
* Now work up through the levels of the tree one at a time, counting how
* many pixels are in nodes preceding the current node.
*/
for (parentPtr = nodePtr->parentPtr ; parentPtr != NULL;
nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) {
Node *nodePtr2;
for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr;
nodePtr2 = nodePtr2->nextPtr) {
if (nodePtr2 == NULL) {
Tcl_Panic("TkBTreePixelsTo couldn't find node");
}
index += nodePtr2->numPixels[pixelReference];
}
}
return index;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeLinesTo --
*
* 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
TkBTreeLinesTo(
const TkText *textPtr, /* Relative to this client of the B-tree. */
TkTextLine *linePtr) /* Pointer to existing line in B-tree. */
{
TkTextLine *linePtr2;
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) {
Tcl_Panic("TkBTreeLinesTo 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) {
Tcl_Panic("TkBTreeLinesTo couldn't find node");
}
index += nodePtr2->numLines;
}
}
if (textPtr != NULL) {
/*
* The index to return must be relative to textPtr, not to the entire
* tree. Take care to never return a negative index when linePtr
* denotes a line before -startline, or an index larger than the
* number of lines in textPtr when linePtr is a line past -endline.
*/
int indexStart, indexEnd;
if (textPtr->start != NULL) {
indexStart = TkBTreeLinesTo(NULL, textPtr->start);
} else {
indexStart = 0;
}
if (textPtr->end != NULL) {
indexEnd = TkBTreeLinesTo(NULL, textPtr->end);
} else {
indexEnd = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL);
}
if (index < indexStart) {
index = 0;
} else if (index > indexEnd) {
index = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
} else {
index -= indexStart;
}
}
return index;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeLinkSegment --
*
* This function adds a new segment to a B-tree at a given location.
*
* Results:
* None.
*
* Side effects:
* SegPtr will be linked into its tree.
*
*----------------------------------------------------------------------
*/
void
TkBTreeLinkSegment(
TkTextSegment *segPtr, /* Pointer to new segment to be added to
* B-tree. Should be completely initialized by
* caller except for nextPtr field. */
TkTextIndex *indexPtr) /* Where to add segment: it gets linked in
* just before the segment indicated here. */
{
TkTextSegment *prevPtr;
prevPtr = SplitSeg(indexPtr);
if (prevPtr == NULL) {
segPtr->nextPtr = indexPtr->linePtr->segPtr;
indexPtr->linePtr->segPtr = segPtr;
} else {
segPtr->nextPtr = prevPtr->nextPtr;
prevPtr->nextPtr = segPtr;
}
CleanupLine(indexPtr->linePtr);
if (tkBTreeDebug) {
TkBTreeCheck(indexPtr->tree);
}
((BTree *)indexPtr->tree)->stateEpoch++;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeUnlinkSegment --
*
* This function unlinks a segment from its line in a B-tree.
*
* Results:
* None.
*
* Side effects:
* SegPtr will be unlinked from linePtr. The segment itself isn't
* modified by this function.
*
*----------------------------------------------------------------------
*/
void
TkBTreeUnlinkSegment(
TkTextSegment *segPtr, /* Segment to be unlinked. */
TkTextLine *linePtr) /* Line that currently contains segment. */
{
TkTextSegment *prevPtr;
if (linePtr->segPtr == segPtr) {
linePtr->segPtr = segPtr->nextPtr;
} else {
prevPtr = linePtr->segPtr;
while (prevPtr->nextPtr != segPtr) {
prevPtr = prevPtr->nextPtr;
if (prevPtr == NULL) {
/*
* Two logical lines merged into one display line through
* eliding of a newline.
*/
linePtr = TkBTreeNextLine(NULL, linePtr);
prevPtr = linePtr->segPtr;
}
}
prevPtr->nextPtr = segPtr->nextPtr;
}
CleanupLine(linePtr);
}
/*
*----------------------------------------------------------------------
*
* TkBTreeTag --
*
* Turn a given tag on or off for a given range of characters in a B-tree
* of text.
*
* Results:
* 1 if the tags on any characters in the range were changed, and zero
* otherwise (i.e. if the tag was already absent (add = 0) or present
* (add = 1) on the index range in question).
*
* Side effects:
* The given tag is added to the given range of characters in the tree or
* removed from all those characters, depending on the "add" argument.
* The structure of the btree is modified enough that index1Ptr and
* index2Ptr are no longer valid after this function returns, and the
* indexes may be modified by this function.
*
*----------------------------------------------------------------------
*/
int
TkBTreeTag(
TkTextIndex *index1Ptr,
/* Indicates first character in range. */
TkTextIndex *index2Ptr,
/* Indicates character just after the last one
* in range. */
TkTextTag *tagPtr, /* Tag to add or remove. */
int add) /* One means add tag to the given range of
* characters; zero means remove the tag from
* the range. */
{
TkTextSegment *segPtr, *prevPtr;
TkTextSearch search;
TkTextLine *cleanupLinePtr;
int oldState, changed, anyChanges = 0;
/*
* See whether the tag is present at the start of the range. If the state
* doesn't already match what we want then add a toggle there.
*/
oldState = TkBTreeCharTagged(index1Ptr, tagPtr);
if ((add != 0) ^ oldState) {
segPtr = (TkTextSegment *)ckalloc(TSEG_SIZE);
segPtr->typePtr = (add) ? &tkTextToggleOnType : &tkTextToggleOffType;
prevPtr = SplitSeg(index1Ptr);
if (prevPtr == NULL) {
segPtr->nextPtr = index1Ptr->linePtr->segPtr;
index1Ptr->linePtr->segPtr = segPtr;
} else {
segPtr->nextPtr = prevPtr->nextPtr;
prevPtr->nextPtr = segPtr;
}
segPtr->size = 0;
segPtr->body.toggle.tagPtr = tagPtr;
segPtr->body.toggle.inNodeCounts = 0;
anyChanges = 1;
}
/*
* Scan the range of characters and delete any internal tag transitions.
* Keep track of what the old state was at the end of the range, and add a
* toggle there if it's needed.
*/
TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
cleanupLinePtr = index1Ptr->linePtr;
while (TkBTreeNextTag(&search)) {
anyChanges = 1;
oldState ^= 1;
segPtr = search.segPtr;
prevPtr = search.curIndex.linePtr->segPtr;
if (prevPtr == segPtr) {
search.curIndex.linePtr->segPtr = segPtr->nextPtr;
} else {
while (prevPtr->nextPtr != segPtr) {
prevPtr = prevPtr->nextPtr;
}
prevPtr->nextPtr = segPtr->nextPtr;
}
if (segPtr->body.toggle.inNodeCounts) {
ChangeNodeToggleCount(search.curIndex.linePtr->parentPtr,
segPtr->body.toggle.tagPtr, -1);
segPtr->body.toggle.inNodeCounts = 0;
changed = 1;
} else {
changed = 0;
}
ckfree(segPtr);
/*
* The code below is a bit tricky. After deleting a toggle we
* eventually have to call CleanupLine, in order to allow character
* segments to be merged together. To do this, we remember in
* cleanupLinePtr a line that needs to be cleaned up, but we don't
* clean it up until we've moved on to a different line. That way the
* cleanup process won't goof up segPtr.
*/
if (cleanupLinePtr != search.curIndex.linePtr) {
CleanupLine(cleanupLinePtr);
cleanupLinePtr = search.curIndex.linePtr;
}
/*
* Quick hack. ChangeNodeToggleCount may move the tag's root location
* around and leave the search in the void. This resets the search.
*/
if (changed) {
TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
}
}
if ((add != 0) ^ oldState) {
segPtr = (TkTextSegment *)ckalloc(TSEG_SIZE);
segPtr->typePtr = (add) ? &tkTextToggleOffType : &tkTextToggleOnType;
prevPtr = SplitSeg(index2Ptr);
if (prevPtr == NULL) {
segPtr->nextPtr = index2Ptr->linePtr->segPtr;
index2Ptr->linePtr->segPtr = segPtr;
} else {
segPtr->nextPtr = prevPtr->nextPtr;
prevPtr->nextPtr = segPtr;
}
segPtr->size = 0;
segPtr->body.toggle.tagPtr = tagPtr;
segPtr->body.toggle.inNodeCounts = 0;
anyChanges = 1;
}
/*
* Cleanup cleanupLinePtr and the last line of the range, if these are
* different.
*/
if (anyChanges) {
CleanupLine(cleanupLinePtr);
if (cleanupLinePtr != index2Ptr->linePtr) {
CleanupLine(index2Ptr->linePtr);
}
((BTree *)index1Ptr->tree)->stateEpoch++;
}
if (tkBTreeDebug) {
TkBTreeCheck(index1Ptr->tree);
}
return anyChanges;
}
/*
*----------------------------------------------------------------------
*
* ChangeNodeToggleCount --
*
* This function increments or decrements the toggle count for a
* particular tag in a particular node and all its ancestors up to the
* per-tag root node.
*
* Results:
* None.
*
* Side effects:
* The toggle count for tag is adjusted up or down by "delta" in nodePtr.
* This routine maintains the tagRootPtr that identifies the root node
* for the tag, moving it up or down the tree as needed.
*
*----------------------------------------------------------------------
*/
static void
ChangeNodeToggleCount(
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). */
{
Summary *summaryPtr, *prevPtr;
Node *node2Ptr;
int rootLevel; /* Level of original tag root. */
tagPtr->toggleCount += delta;
if (tagPtr->tagRootPtr == NULL) {
tagPtr->tagRootPtr = nodePtr;
return;
}
/*
* Note the level of the existing root for the tag so we can detect if it
* needs to be moved because of the toggle count change.
*/
rootLevel = tagPtr->tagRootPtr->level;
/*
* Iterate over the node and its ancestors up to the tag root, adjusting
* summary counts at each node and moving the tag's root upwards if
* necessary.
*/
for ( ; nodePtr != tagPtr->tagRootPtr; 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) {
break;
}
}
if (summaryPtr != NULL) {
summaryPtr->toggleCount += delta;
if (summaryPtr->toggleCount > 0 &&
summaryPtr->toggleCount < tagPtr->toggleCount) {
continue;
}
if (summaryPtr->toggleCount != 0) {
/*
* Should never find a node with max toggle count at this
* point (there shouldn't have been a summary entry in the
* first place).
*/
Tcl_Panic("ChangeNodeToggleCount: bad toggle count (%d) max (%d)",
summaryPtr->toggleCount, tagPtr->toggleCount);
}
/*
* Zero toggle count; must remove this tag from the list.
*/
if (prevPtr == NULL) {
nodePtr->summaryPtr = summaryPtr->nextPtr;
} else {
prevPtr->nextPtr = summaryPtr->nextPtr;
}
ckfree(summaryPtr);
} else {
/*
* This tag isn't currently in the summary information list.
*/
if (rootLevel == nodePtr->level) {
/*
* The old tag root is at the same level in the tree as this
* node, but it isn't at this node. Move the tag root up a
* level, in the hopes that it will now cover this node as
* well as the old root (if not, we'll move it up again the
* next time through the loop). To push it up one level we
* copy the original toggle count into the summary information
* at the old root and change the root to its parent node.
*/
Node *rootNodePtr = tagPtr->tagRootPtr;
summaryPtr = (Summary *)ckalloc(sizeof(Summary));
summaryPtr->tagPtr = tagPtr;
summaryPtr->toggleCount = tagPtr->toggleCount - delta;
summaryPtr->nextPtr = rootNodePtr->summaryPtr;
rootNodePtr->summaryPtr = summaryPtr;
rootNodePtr = rootNodePtr->parentPtr;
rootLevel = rootNodePtr->level;
tagPtr->tagRootPtr = rootNodePtr;
}
summaryPtr = (Summary *)ckalloc(sizeof(Summary));
summaryPtr->tagPtr = tagPtr;
summaryPtr->toggleCount = delta;
summaryPtr->nextPtr = nodePtr->summaryPtr;
nodePtr->summaryPtr = summaryPtr;
}
}
/*
* If we've decremented the toggle count, then it may be necessary to push
* the tag root down one or more levels.
*/
if (delta >= 0) {
return;
}
if (tagPtr->toggleCount == 0) {
tagPtr->tagRootPtr = NULL;
return;
}
nodePtr = tagPtr->tagRootPtr;
while (nodePtr->level > 0) {
/*
* See if a single child node accounts for all of the tag's toggles.
* If so, push the root down one level.
*/
for (node2Ptr = nodePtr->children.nodePtr;
node2Ptr != NULL ;
node2Ptr = node2Ptr->nextPtr) {
for (prevPtr = NULL, summaryPtr = node2Ptr->summaryPtr;
summaryPtr != NULL;
prevPtr = summaryPtr, summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr->tagPtr == tagPtr) {
break;
}
}
if (summaryPtr == NULL) {
continue;
}
if (summaryPtr->toggleCount != tagPtr->toggleCount) {
/*
* No node has all toggles, so the root is still valid.
*/
return;
}
/*
* This node has all the toggles, so push down the root.
*/
if (prevPtr == NULL) {
node2Ptr->summaryPtr = summaryPtr->nextPtr;
} else {
prevPtr->nextPtr = summaryPtr->nextPtr;
}
ckfree(summaryPtr);
tagPtr->tagRootPtr = node2Ptr;
break;
}
nodePtr = tagPtr->tagRootPtr;
}
}
/*
*----------------------------------------------------------------------
*
* FindTagStart --
*
* Find the start of the first range of a tag.
*
* Results:
* The return value is a pointer to the first tag toggle segment for the
* tag. This can be either a tagon or tagoff segments because of the way
* TkBTreeAdd removes a tag. Sets *indexPtr to be the index of the tag
* toggle.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static TkTextSegment *
FindTagStart(
TkTextBTree tree, /* Tree to search within. */
TkTextTag *tagPtr, /* Tag to search for. */
TkTextIndex *indexPtr) /* Return - index information. */
{
Node *nodePtr;
TkTextLine *linePtr;
TkTextSegment *segPtr;
Summary *summaryPtr;
int offset;
nodePtr = tagPtr->tagRootPtr;
if (nodePtr == NULL) {
return NULL;
}
/*
* Search from the root of the subtree that contains the tag down to the
* level 0 node.
*/
while (nodePtr && nodePtr->level > 0) {
for (nodePtr = nodePtr->children.nodePtr ; nodePtr != NULL;
nodePtr = nodePtr->nextPtr) {
for (summaryPtr = nodePtr->summaryPtr ; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr->tagPtr == tagPtr) {
goto gotNodeWithTag;
}
}
}
gotNodeWithTag:
continue;
}
if (nodePtr == NULL) {
return NULL;
}
/*
* Work through the lines attached to the level-0 node.
*/
for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
linePtr = linePtr->nextPtr) {
for (offset = 0, segPtr = linePtr->segPtr ; segPtr != NULL;
offset += segPtr->size, segPtr = segPtr->nextPtr) {
if (((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType))
&& (segPtr->body.toggle.tagPtr == tagPtr)) {
/*
* It is possible that this is a tagoff tag, but that gets
* cleaned up later.
*/
indexPtr->tree = tree;
indexPtr->linePtr = linePtr;
indexPtr->byteIndex = offset;
return segPtr;
}
}
}
return NULL;
}
/*
*----------------------------------------------------------------------
*
* FindTagEnd --
*
* Find the end of the last range of a tag.
*
* Results:
* The return value is a pointer to the last tag toggle segment for the
* tag. This can be either a tagon or tagoff segments because of the way
* TkBTreeAdd removes a tag. Sets *indexPtr to be the index of the tag
* toggle.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static TkTextSegment *
FindTagEnd(
TkTextBTree tree, /* Tree to search within. */
TkTextTag *tagPtr, /* Tag to search for. */
TkTextIndex *indexPtr) /* Return - index information. */
{
Node *nodePtr, *lastNodePtr;
TkTextLine *linePtr ,*lastLinePtr;
TkTextSegment *segPtr, *lastSegPtr, *last2SegPtr;
Summary *summaryPtr;
int lastoffset, lastoffset2, offset;
nodePtr = tagPtr->tagRootPtr;
if (nodePtr == NULL) {
return NULL;
}
/*
* Search from the root of the subtree that contains the tag down to the
* level 0 node.
*/
while (nodePtr && nodePtr->level > 0) {
for (lastNodePtr = NULL, nodePtr = nodePtr->children.nodePtr ;
nodePtr != NULL; nodePtr = nodePtr->nextPtr) {
for (summaryPtr = nodePtr->summaryPtr ; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr->tagPtr == tagPtr) {
lastNodePtr = nodePtr;
break;
}
}
}
nodePtr = lastNodePtr;
}
if (nodePtr == NULL) {
return NULL;
}
/*
* Work through the lines attached to the level-0 node.
*/
last2SegPtr = NULL;
lastoffset2 = 0;
lastoffset = 0;
for (lastLinePtr = NULL, linePtr = nodePtr->children.linePtr;
linePtr != NULL; linePtr = linePtr->nextPtr) {
for (offset = 0, lastSegPtr = NULL, segPtr = linePtr->segPtr ;
segPtr != NULL;
offset += segPtr->size, segPtr = segPtr->nextPtr) {
if (((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType))
&& (segPtr->body.toggle.tagPtr == tagPtr)) {
lastSegPtr = segPtr;
lastoffset = offset;
}
}
if (lastSegPtr != NULL) {
lastLinePtr = linePtr;
last2SegPtr = lastSegPtr;
lastoffset2 = lastoffset;
}
}
indexPtr->tree = tree;
indexPtr->linePtr = lastLinePtr;
indexPtr->byteIndex = lastoffset2;
return last2SegPtr;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeStartSearch --
*
* This function 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 or TkBTreePrevTag will return information about the
* locations of tag transitions. Note that TkBTreeNextTag or
* TkBTreePrevTag must be called to get the first transition. Note:
* unlike TkBTreeNextTag and TkBTreePrevTag, this routine does not
* guarantee that searchPtr->curIndex is equal to *index1Ptr. It may be
* greater than that if *index1Ptr is less than the first tag transition.
*
*----------------------------------------------------------------------
*/
void
TkBTreeStartSearch(
TkTextIndex *index1Ptr, /* Search starts here. Tag toggles at this
* position will not be returned. */
TkTextIndex *index2Ptr, /* Search stops here. Tag toggles at this
* position *will* be returned. */
TkTextTag *tagPtr, /* Tag to search for. NULL means search for
* any tag. */
TkTextSearch *searchPtr)
/* Where to store information about search's
* progress. */
{
int offset;
TkTextIndex index0; /* First index of the tag. */
TkTextSegment *seg0Ptr; /* First segment of the tag. */
/*
* Find the segment that contains the first toggle for the tag. This may
* become the starting point in the search.
*/
seg0Ptr = FindTagStart(index1Ptr->tree, tagPtr, &index0);
if (seg0Ptr == NULL) {
/*
* Even though there are no toggles, the display code still uses the
* search curIndex, so initialize that anyway.
*/
searchPtr->linesLeft = 0;
searchPtr->curIndex = *index1Ptr;
searchPtr->segPtr = NULL;
searchPtr->nextPtr = NULL;
return;
}
if (TkTextIndexCmp(index1Ptr, &index0) < 0) {
/*
* Adjust start of search up to the first range of the tag.
*/
searchPtr->curIndex = index0;
searchPtr->segPtr = NULL;
searchPtr->nextPtr = seg0Ptr; /* Will be returned by NextTag. */
index1Ptr = &index0;
} else {
searchPtr->curIndex = *index1Ptr;
searchPtr->segPtr = NULL;
searchPtr->nextPtr = TkTextIndexToSeg(index1Ptr, &offset);
searchPtr->curIndex.byteIndex -= offset;
}
searchPtr->lastPtr = TkTextIndexToSeg(index2Ptr, NULL);
searchPtr->tagPtr = tagPtr;
searchPtr->linesLeft = TkBTreeLinesTo(NULL, index2Ptr->linePtr) + 1
- TkBTreeLinesTo(NULL, index1Ptr->linePtr);
searchPtr->allTags = (tagPtr == NULL);
if (searchPtr->linesLeft == 1) {
/*
* Starting and stopping segments are in the same line; mark the
* search as over immediately if the second segment is before the
* first. A search does not return a toggle at the very start of the
* range, unless the range is artificially moved up to index0.
*/
if (((index1Ptr == &index0) &&
(index1Ptr->byteIndex > index2Ptr->byteIndex)) ||
((index1Ptr != &index0) &&
(index1Ptr->byteIndex >= index2Ptr->byteIndex))) {
searchPtr->linesLeft = 0;
}
}
}
/*
*----------------------------------------------------------------------
*
* TkBTreeStartSearchBack --
*
* This function sets up a search backwards for tag transitions involving
* a given tag (or all tags) in a given range of the text. In the normal
* case the first index (*index1Ptr) is beyond the second index
* (*index2Ptr).
*
* Results:
* None.
*
* Side effects:
* The information at *searchPtr is set up so that subsequent calls to
* TkBTreePrevTag will return information about the locations of tag
* transitions. Note that TkBTreePrevTag must be called to get the first
* transition. Note: unlike TkBTreeNextTag and TkBTreePrevTag, this
* routine does not guarantee that searchPtr->curIndex is equal to
* *index1Ptr. It may be less than that if *index1Ptr is greater than the
* last tag transition.
*
*----------------------------------------------------------------------
*/
void
TkBTreeStartSearchBack(
TkTextIndex *index1Ptr, /* Search starts here. Tag toggles at this
* position will not be returned. */
TkTextIndex *index2Ptr, /* Search stops here. Tag toggles at this
* position *will* be returned. */
TkTextTag *tagPtr, /* Tag to search for. NULL means search for
* any tag. */
TkTextSearch *searchPtr)
/* Where to store information about search's
* progress. */
{
int offset;
TkTextIndex index0; /* Last index of the tag. */
TkTextIndex backOne; /* One character before starting index. */
TkTextSegment *seg0Ptr; /* Last segment of the tag. */
/*
* Find the segment that contains the last toggle for the tag. This may
* become the starting point in the search.
*/
seg0Ptr = FindTagEnd(index1Ptr->tree, tagPtr, &index0);
if (seg0Ptr == NULL) {
/*
* Even though there are no toggles, the display code still uses the
* search curIndex, so initialize that anyway.
*/
searchPtr->linesLeft = 0;
searchPtr->curIndex = *index1Ptr;
searchPtr->segPtr = NULL;
searchPtr->nextPtr = NULL;
return;
}
/*
* Adjust the start of the search so it doesn't find any tag toggles
* that are right at the index specified by the user.
*/
if (TkTextIndexCmp(index1Ptr, &index0) > 0) {
searchPtr->curIndex = index0;
index1Ptr = &index0;
} else {
TkTextIndexBackChars(NULL, index1Ptr, 1, &searchPtr->curIndex,
COUNT_INDICES);
}
searchPtr->segPtr = NULL;
searchPtr->nextPtr = TkTextIndexToSeg(&searchPtr->curIndex, &offset);
searchPtr->curIndex.byteIndex -= offset;
/*
* Adjust the end of the search so it does find toggles that are right at
* the second index specified by the user.
*/
if ((TkBTreeLinesTo(NULL, index2Ptr->linePtr) == 0) &&
(index2Ptr->byteIndex == 0)) {
backOne = *index2Ptr;
searchPtr->lastPtr = NULL; /* Signals special case for 1.0. */
} else {
TkTextIndexBackChars(NULL, index2Ptr, 1, &backOne, COUNT_INDICES);
searchPtr->lastPtr = TkTextIndexToSeg(&backOne, NULL);
}
searchPtr->tagPtr = tagPtr;
searchPtr->linesLeft = TkBTreeLinesTo(NULL, index1Ptr->linePtr) + 1
- TkBTreeLinesTo(NULL, backOne.linePtr);
searchPtr->allTags = (tagPtr == NULL);
if (searchPtr->linesLeft == 1) {
/*
* Starting and stopping segments are in the same line; mark the
* search as over immediately if the second segment is after the
* first.
*/
if (index1Ptr->byteIndex <= backOne.byteIndex) {
searchPtr->linesLeft = 0;
}
}
}
/*
*----------------------------------------------------------------------
*
* TkBTreeNextTag --
*
* Once a tag search has begun, successive calls to this function return
* successive tag toggles. Note: it is NOT SAFE to call this function if
* characters have been inserted into or deleted from the B-tree since
* the call to TkBTreeStartSearch.
*
* Results:
* The return value is 1 if another toggle was found that met the
* criteria specified in the call to TkBTreeStartSearch; in this case
* searchPtr->curIndex gives the toggle's position and
* searchPtr->curTagPtr points to its segment. 0 is returned if no more
* matching tag transitions were found; in this case searchPtr->curIndex
* is the same as searchPtr->stopIndex.
*
* Side effects:
* Information in *searchPtr is modified to update the state of the
* search and indicate where the next tag toggle is located.
*
*----------------------------------------------------------------------
*/
int
TkBTreeNextTag(
TkTextSearch *searchPtr)
/* Information about search in progress; must
* have been set up by call to
* TkBTreeStartSearch. */
{
TkTextSegment *segPtr;
Node *nodePtr;
Summary *summaryPtr;
if (searchPtr->linesLeft <= 0) {
goto searchOver;
}
/*
* The outermost loop iterates over lines that may potentially contain a
* relevant tag transition, starting from the current segment in the
* current line.
*/
segPtr = searchPtr->nextPtr;
while (1) {
/*
* Check for more tags on the current line.
*/
for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
if (segPtr == searchPtr->lastPtr) {
goto searchOver;
}
if (((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType))
&& (searchPtr->allTags
|| (segPtr->body.toggle.tagPtr == searchPtr->tagPtr))) {
searchPtr->segPtr = segPtr;
searchPtr->nextPtr = segPtr->nextPtr;
searchPtr->tagPtr = segPtr->body.toggle.tagPtr;
return 1;
}
searchPtr->curIndex.byteIndex += segPtr->size;
}
/*
* See if there are more lines associated with the current parent
* node. If so, go back to the top of the loop to search the next one.
*/
nodePtr = searchPtr->curIndex.linePtr->parentPtr;
searchPtr->curIndex.linePtr = searchPtr->curIndex.linePtr->nextPtr;
searchPtr->linesLeft--;
if (searchPtr->linesLeft <= 0) {
goto searchOver;
}
if (searchPtr->curIndex.linePtr != NULL) {
segPtr = searchPtr->curIndex.linePtr->segPtr;
searchPtr->curIndex.byteIndex = 0;
continue;
}
if (nodePtr == searchPtr->tagPtr->tagRootPtr) {
goto searchOver;
}
/*
* Search across and up through the B-tree's node hierarchy looking
* for the next node that has a relevant tag transition somewhere in
* its subtree. Be sure to update linesLeft as we skip over large
* chunks of lines.
*/
while (1) {
while (nodePtr->nextPtr == NULL) {
if (nodePtr->parentPtr == NULL ||
nodePtr->parentPtr == searchPtr->tagPtr->tagRootPtr) {
goto searchOver;
}
nodePtr = nodePtr->parentPtr;
}
nodePtr = nodePtr->nextPtr;
for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if ((searchPtr->allTags) ||
(summaryPtr->tagPtr == searchPtr->tagPtr)) {
goto gotNodeWithTag;
}
}
searchPtr->linesLeft -= nodePtr->numLines;
}
/*
* At this point we've found a subtree that has a relevant tag
* transition. Now search down (and across) through that subtree to
* find the first level-0 node that has a relevant tag transition.
*/
gotNodeWithTag:
while (nodePtr->level > 0) {
for (nodePtr = nodePtr->children.nodePtr; ;
nodePtr = nodePtr->nextPtr) {
for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if ((searchPtr->allTags)
|| (summaryPtr->tagPtr == searchPtr->tagPtr)) {
/*
* Would really like a multi-level continue here...
*/
goto nextChild;
}
}
searchPtr->linesLeft -= nodePtr->numLines;
if (nodePtr->nextPtr == NULL) {
Tcl_Panic("TkBTreeNextTag found incorrect tag summary info");
}
}
nextChild:
continue;
}
/*
* Now we're down to a level-0 node that contains a line that contains
* a relevant tag transition. Set up line information and go back to
* the beginning of the loop to search through lines.
*/
searchPtr->curIndex.linePtr = nodePtr->children.linePtr;
searchPtr->curIndex.byteIndex = 0;
segPtr = searchPtr->curIndex.linePtr->segPtr;
if (searchPtr->linesLeft <= 0) {
goto searchOver;
}
continue;
}
searchOver:
searchPtr->linesLeft = 0;
searchPtr->segPtr = NULL;
return 0;
}
/*
*----------------------------------------------------------------------
*
* TkBTreePrevTag --
*
* Once a tag search has begun, successive calls to this function return
* successive tag toggles in the reverse direction. Note: it is NOT SAFE
* to call this function if characters have been inserted into or deleted
* from the B-tree since the call to TkBTreeStartSearch.
*
* Results:
* The return value is 1 if another toggle was found that met the
* criteria specified in the call to TkBTreeStartSearch; in this case
* searchPtr->curIndex gives the toggle's position and
* searchPtr->curTagPtr points to its segment. 0 is returned if no more
* matching tag transitions were found; in this case searchPtr->curIndex
* is the same as searchPtr->stopIndex.
*
* Side effects:
* Information in *searchPtr is modified to update the state of the
* search and indicate where the next tag toggle is located.
*
*----------------------------------------------------------------------
*/
int
TkBTreePrevTag(
TkTextSearch *searchPtr)
/* Information about search in progress; must
* have been set up by call to
* TkBTreeStartSearch. */
{
TkTextSegment *segPtr, *prevPtr;
TkTextLine *linePtr, *prevLinePtr;
Node *nodePtr, *node2Ptr, *prevNodePtr;
Summary *summaryPtr;
int byteIndex, linesSkipped;
int pastLast; /* Saw last marker during scan. */
if (searchPtr->linesLeft <= 0) {
goto searchOver;
}
/*
* The outermost loop iterates over lines that may potentially contain a
* relevant tag transition, starting from the current segment in the
* current line. "nextPtr" is maintained as the last segment in a line
* that we can look at.
*/
while (1) {
/*
* Check for the last toggle before the current segment on this line.
*/
byteIndex = 0;
if (searchPtr->lastPtr == NULL) {
/*
* Search back to the very beginning, so pastLast is irrelevent.
*/
pastLast = 1;
} else {
pastLast = 0;
}
for (prevPtr = NULL, segPtr = searchPtr->curIndex.linePtr->segPtr ;
segPtr != NULL && segPtr != searchPtr->nextPtr;
segPtr = segPtr->nextPtr) {
if (((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType))
&& (searchPtr->allTags
|| (segPtr->body.toggle.tagPtr == searchPtr->tagPtr))) {
prevPtr = segPtr;
searchPtr->curIndex.byteIndex = byteIndex;
}
if (segPtr == searchPtr->lastPtr) {
prevPtr = NULL; /* Segments earlier than last don't
* count. */
pastLast = 1;
}
byteIndex += segPtr->size;
}
if (prevPtr != NULL) {
if (searchPtr->linesLeft == 1 && !pastLast) {
/*
* We found a segment that is before the stopping index. Note
* that it is OK if prevPtr == lastPtr.
*/
goto searchOver;
}
searchPtr->segPtr = prevPtr;
searchPtr->nextPtr = prevPtr;
searchPtr->tagPtr = prevPtr->body.toggle.tagPtr;
return 1;
}
searchPtr->linesLeft--;
if (searchPtr->linesLeft <= 0) {
goto searchOver;
}
/*
* 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 previous
* one.
*/
nodePtr = searchPtr->curIndex.linePtr->parentPtr;
for (prevLinePtr = NULL, linePtr = nodePtr->children.linePtr;
linePtr != NULL && linePtr != searchPtr->curIndex.linePtr;
prevLinePtr = linePtr, linePtr = linePtr->nextPtr) {
/* empty loop body */ ;
}
if (prevLinePtr != NULL) {
searchPtr->curIndex.linePtr = prevLinePtr;
searchPtr->nextPtr = NULL;
continue;
}
if (nodePtr == searchPtr->tagPtr->tagRootPtr) {
goto searchOver;
}
/*
* Search across and up through the B-tree's node hierarchy looking
* for the previous node that has a relevant tag transition somewhere
* in its subtree. The search and line counting is trickier with/out
* back pointers. We'll scan all the nodes under a parent up to the
* current node, searching all of them for tag state. The last one we
* find, if any, is recorded in prevNodePtr, and any nodes past
* prevNodePtr that don't have tag state increment linesSkipped.
*/
while (1) {
for (prevNodePtr = NULL, linesSkipped = 0,
node2Ptr = nodePtr->parentPtr->children.nodePtr ;
node2Ptr != nodePtr; node2Ptr = node2Ptr->nextPtr) {
for (summaryPtr = node2Ptr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if ((searchPtr->allTags) ||
(summaryPtr->tagPtr == searchPtr->tagPtr)) {
prevNodePtr = node2Ptr;
linesSkipped = 0;
goto keepLooking;
}
}
linesSkipped += node2Ptr->numLines;
keepLooking:
continue;
}
if (prevNodePtr != NULL) {
nodePtr = prevNodePtr;
searchPtr->linesLeft -= linesSkipped;
goto gotNodeWithTag;
}
nodePtr = nodePtr->parentPtr;
if (nodePtr->parentPtr == NULL ||
nodePtr == searchPtr->tagPtr->tagRootPtr) {
goto searchOver;
}
}
/*
* 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 last level-0 node that has a relevant tag transition.
*/
gotNodeWithTag:
while (nodePtr->level > 0) {
for (linesSkipped = 0, prevNodePtr = NULL,
nodePtr = nodePtr->children.nodePtr; nodePtr != NULL ;
nodePtr = nodePtr->nextPtr) {
for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if ((searchPtr->allTags)
|| (summaryPtr->tagPtr == searchPtr->tagPtr)) {
prevNodePtr = nodePtr;
linesSkipped = 0;
goto keepLooking2;
}
}
linesSkipped += nodePtr->numLines;
keepLooking2:
continue;
}
if (prevNodePtr == NULL) {
Tcl_Panic("TkBTreePrevTag found incorrect tag summary info");
}
searchPtr->linesLeft -= linesSkipped;
nodePtr = prevNodePtr;
}
/*
* 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. We start with
* the last line below the node.
*/
for (prevLinePtr = NULL, linePtr = nodePtr->children.linePtr;
linePtr != NULL ;
prevLinePtr = linePtr, linePtr = linePtr->nextPtr) {
/* empty loop body */ ;
}
searchPtr->curIndex.linePtr = prevLinePtr;
searchPtr->curIndex.byteIndex = 0;
if (searchPtr->linesLeft <= 0) {
goto searchOver;
}
continue;
}
searchOver:
searchPtr->linesLeft = 0;
searchPtr->segPtr = NULL;
return 0;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeCharTagged --
*
* Determine whether a particular character has a particular tag.
*
* Results:
* The return value is 1 if the given tag is in effect at the character
* given by linePtr and ch, and 0 otherwise.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkBTreeCharTagged(
const TkTextIndex *indexPtr,/* Indicates a character position at which to
* check for a tag. */
TkTextTag *tagPtr) /* Tag of interest. */
{
Node *nodePtr;
TkTextLine *siblingLinePtr;
TkTextSegment *segPtr;
TkTextSegment *toggleSegPtr;
int toggles, index;
/*
* Check for toggles for the tag in indexPtr's line but before indexPtr.
* If there is one, its type indicates whether or not the character is
* tagged.
*/
toggleSegPtr = NULL;
for (index = 0, segPtr = indexPtr->linePtr->segPtr;
(index + segPtr->size) <= indexPtr->byteIndex;
index += segPtr->size, segPtr = segPtr->nextPtr) {
if (((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType))
&& (segPtr->body.toggle.tagPtr == tagPtr)) {
toggleSegPtr = segPtr;
}
}
if (toggleSegPtr != NULL) {
return (toggleSegPtr->typePtr == &tkTextToggleOnType);
}
/*
* No toggle in this line. Look for toggles for the tag in lines that are
* predecessors of indexPtr->linePtr but under the same level-0 node.
*/
for (siblingLinePtr = indexPtr->linePtr->parentPtr->children.linePtr;
siblingLinePtr != indexPtr->linePtr;
siblingLinePtr = siblingLinePtr->nextPtr) {
for (segPtr = siblingLinePtr->segPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
if (((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType))
&& (segPtr->body.toggle.tagPtr == tagPtr)) {
toggleSegPtr = segPtr;
}
}
}
if (toggleSegPtr != NULL) {
return (toggleSegPtr->typePtr == &tkTextToggleOnType);
}
/*
* No toggle in this node. Scan upwards through the ancestors of this
* node, counting the number of toggles of the given tag in siblings that
* precede that node.
*/
toggles = 0;
for (nodePtr = indexPtr->linePtr->parentPtr; nodePtr->parentPtr != NULL;
nodePtr = nodePtr->parentPtr) {
Node *siblingPtr;
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;
}
}
}
if (nodePtr == tagPtr->tagRootPtr) {
break;
}
}
/*
* 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.
*
*----------------------------------------------------------------------
*/
TkTextTag **
TkBTreeGetTags(
const TkTextIndex *indexPtr,/* Indicates a particular position in the
* B-tree. */
const TkText *textPtr, /* If non-NULL, then only return tags for this
* text widget (when there are peer
* widgets). */
int *numTagsPtr) /* Store number of tags found at this
* location. */
{
Node *nodePtr;
TkTextLine *siblingLinePtr;
TkTextSegment *segPtr;
TkTextLine *linePtr;
int src, dst, index;
TagInfo tagInfo;
#define NUM_TAG_INFOS 10
tagInfo.numTags = 0;
tagInfo.arraySize = NUM_TAG_INFOS;
tagInfo.tagPtrs = (TkTextTag **)ckalloc(NUM_TAG_INFOS * sizeof(TkTextTag *));
tagInfo.counts = (int *)ckalloc(NUM_TAG_INFOS * sizeof(int));
/*
* Record tag toggles within the line of indexPtr but preceding indexPtr.
*/
linePtr = indexPtr->linePtr;
index = 0;
segPtr = linePtr->segPtr;
while ((index + segPtr->size) <= indexPtr->byteIndex) {
if ((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType)) {
IncCount(segPtr->body.toggle.tagPtr, 1, &tagInfo);
}
index += segPtr->size;
segPtr = segPtr->nextPtr;
if (segPtr == NULL) {
/*
* Two logical lines merged into one display line through eliding
* of a newline.
*/
linePtr = TkBTreeNextLine(NULL, linePtr);
segPtr = linePtr->segPtr;
}
}
/*
* Record toggles for tags in lines that are predecessors of
* indexPtr->linePtr but under the same level-0 node.
*/
for (siblingLinePtr = indexPtr->linePtr->parentPtr->children.linePtr;
siblingLinePtr != indexPtr->linePtr;
siblingLinePtr = siblingLinePtr->nextPtr) {
for (segPtr = siblingLinePtr->segPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
if ((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType)) {
IncCount(segPtr->body.toggle.tagPtr, 1, &tagInfo);
}
}
}
/*
* For each node in the ancestry of this line, record tag toggles for all
* siblings that precede that node.
*/
for (nodePtr = indexPtr->linePtr->parentPtr; nodePtr->parentPtr != NULL;
nodePtr = nodePtr->parentPtr) {
Node *siblingPtr;
Summary *summaryPtr;
for (siblingPtr = nodePtr->parentPtr->children.nodePtr;
siblingPtr != nodePtr; siblingPtr = siblingPtr->nextPtr) {
for (summaryPtr = siblingPtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr->toggleCount & 1) {
IncCount(summaryPtr->tagPtr, summaryPtr->toggleCount,
&tagInfo);
}
}
}
}
/*
* Go through the tag information and squash out all of the tags that have
* even toggle counts (these tags exist before the point of interest, but
* not at the desired character itself). Also squash out all tags that
* don't belong to the requested widget.
*/
for (src = 0, dst = 0; src < tagInfo.numTags; src++) {
if (tagInfo.counts[src] & 1) {
const TkText *tagTextPtr = tagInfo.tagPtrs[src]->textPtr;
if (tagTextPtr==NULL || textPtr==NULL || tagTextPtr==textPtr) {
tagInfo.tagPtrs[dst] = tagInfo.tagPtrs[src];
dst++;
}
}
}
*numTagsPtr = dst;
ckfree(tagInfo.counts);
if (dst == 0) {
ckfree(tagInfo.tagPtrs);
return NULL;
}
return tagInfo.tagPtrs;
}
/*
*----------------------------------------------------------------------
*
* TkTextIsElided --
*
* Special case to just return information about elided attribute.
* Specialized from TkBTreeGetTags(indexPtr, textPtr, numTagsPtr) and
* GetStyle(textPtr, indexPtr). Just need to keep track of invisibility
* settings for each priority, pick highest one active at end.
*
* Note that this returns all elide information up to and including the
* given index (quite obviously). However, this does mean that if
* indexPtr is a line-start and one then iterates from the beginning of
* that line forwards, one will actually revisit the segPtrs of size zero
* (for tag toggling, for example) which have already been seen here.
*
* For this reason we fill in the fields 'segPtr' and 'segOffset' of
* elideInfo, enabling our caller easily to calculate incremental changes
* from where we left off.
*
* Results:
* Returns whether this text should be elided or not.
*
* Optionally returns more detailed information in elideInfo.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkTextIsElided(
const TkText *textPtr, /* Overall information about text widget. */
const TkTextIndex *indexPtr,/* The character in the text for which display
* information is wanted. */
TkTextElideInfo *elideInfo) /* NULL or a pointer to a structure in which
* indexPtr's elide state will be stored and
* returned. */
{
Node *nodePtr;
TkTextLine *siblingLinePtr;
TkTextSegment *segPtr;
TkTextTag *tagPtr = NULL;
int i, index;
TkTextElideInfo *infoPtr;
TkTextLine *linePtr;
int elide;
if (elideInfo == NULL) {
infoPtr = (TkTextElideInfo *)ckalloc(sizeof(TkTextElideInfo));
} else {
infoPtr = elideInfo;
}
infoPtr->elide = 0; /* If nobody says otherwise, it's visible. */
infoPtr->tagCnts = infoPtr->deftagCnts;
infoPtr->tagPtrs = infoPtr->deftagPtrs;
infoPtr->numTags = textPtr->sharedTextPtr->numTags;
/*
* Almost always avoid malloc, so stay out of system calls.
*/
if (LOTSA_TAGS < infoPtr->numTags) {
infoPtr->tagCnts = (int *)ckalloc(sizeof(int) * infoPtr->numTags);
infoPtr->tagPtrs = (TkTextTag **)ckalloc(sizeof(TkTextTag *) * infoPtr->numTags);
}
for (i=0; i<infoPtr->numTags; i++) {
infoPtr->tagCnts[i] = 0;
}
/*
* Record tag toggles within the line of indexPtr but preceding indexPtr.
*/
index = 0;
linePtr = indexPtr->linePtr;
segPtr = linePtr->segPtr;
while ((index + segPtr->size) <= indexPtr->byteIndex) {
if ((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType)) {
tagPtr = segPtr->body.toggle.tagPtr;
if (tagPtr->elideString != NULL) {
infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
infoPtr->tagCnts[tagPtr->priority]++;
}
}
index += segPtr->size;
segPtr = segPtr->nextPtr;
if (segPtr == NULL) {
/*
* Two logical lines merged into one display line through eliding
* of a newline.
*/
linePtr = TkBTreeNextLine(NULL, linePtr);
segPtr = linePtr->segPtr;
}
}
/*
* Store the first segPtr we haven't examined completely so that our
* caller knows where to start.
*/
infoPtr->segPtr = segPtr;
infoPtr->segOffset = index;
/*
* Record toggles for tags in lines that are predecessors of
* indexPtr->linePtr but under the same level-0 node.
*/
for (siblingLinePtr = indexPtr->linePtr->parentPtr->children.linePtr;
siblingLinePtr != indexPtr->linePtr;
siblingLinePtr = siblingLinePtr->nextPtr) {
for (segPtr = siblingLinePtr->segPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
if ((segPtr->typePtr == &tkTextToggleOnType)
|| (segPtr->typePtr == &tkTextToggleOffType)) {
tagPtr = segPtr->body.toggle.tagPtr;
if (tagPtr->elideString != NULL) {
infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
infoPtr->tagCnts[tagPtr->priority]++;
}
}
}
}
/*
* For each node in the ancestry of this line, record tag toggles for all
* siblings that precede that node.
*/
for (nodePtr = indexPtr->linePtr->parentPtr; nodePtr->parentPtr != NULL;
nodePtr = nodePtr->parentPtr) {
Node *siblingPtr;
Summary *summaryPtr;
for (siblingPtr = nodePtr->parentPtr->children.nodePtr;
siblingPtr != nodePtr; siblingPtr = siblingPtr->nextPtr) {
for (summaryPtr = siblingPtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr->toggleCount & 1) {
tagPtr = summaryPtr->tagPtr;
if (tagPtr->elideString != NULL) {
infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
infoPtr->tagCnts[tagPtr->priority] +=
summaryPtr->toggleCount;
}
}
}
}
}
/*
* Now traverse from highest priority to lowest, take elided value from
* first odd count (= on).
*/
infoPtr->elidePriority = -1;
for (i = infoPtr->numTags-1; i >=0; i--) {
if (infoPtr->tagCnts[i] & 1) {
infoPtr->elide = infoPtr->tagPtrs[i]->elide;
/*
* Note: i == infoPtr->tagPtrs[i]->priority
*/
infoPtr->elidePriority = i;
break;
}
}
elide = infoPtr->elide;
if (elideInfo == NULL) {
if (LOTSA_TAGS < infoPtr->numTags) {
ckfree(infoPtr->tagCnts);
ckfree(infoPtr->tagPtrs);
}
ckfree(infoPtr);
}
return elide;
}
/*
*----------------------------------------------------------------------
*
* TkTextFreeElideInfo --
*
* This is a utility function used to free up any memory allocated by the
* TkTextIsElided function above.
*
* Results:
* None.
*
* Side effects:
* Memory may be freed.
*
*----------------------------------------------------------------------
*/
void
TkTextFreeElideInfo(
TkTextElideInfo *elideInfo) /* Free any allocated memory in this
* structure. */
{
if (LOTSA_TAGS < elideInfo->numTags) {
ckfree(elideInfo->tagCnts);
ckfree(elideInfo->tagPtrs);
}
}
/*
*----------------------------------------------------------------------
*
* IncCount --
*
* This is a utility function 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(
TkTextTag *tagPtr, /* Handle for tag. */
int inc, /* Amount by which to increment tag count. */
TagInfo *tagInfoPtr) /* Holds cumulative information about tags;
* increment count here. */
{
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(newSize * sizeof(TkTextTag *));
memcpy(newTags, tagInfoPtr->tagPtrs,
tagInfoPtr->arraySize * sizeof(TkTextTag *));
ckfree(tagInfoPtr->tagPtrs);
tagInfoPtr->tagPtrs = newTags;
newCounts = (int *)ckalloc(newSize * sizeof(int));
memcpy(newCounts, tagInfoPtr->counts,
tagInfoPtr->arraySize * sizeof(int));
ckfree(tagInfoPtr->counts);
tagInfoPtr->counts = newCounts;
tagInfoPtr->arraySize = newSize;
}
tagInfoPtr->tagPtrs[tagInfoPtr->numTags] = tagPtr;
tagInfoPtr->counts[tagInfoPtr->numTags] = inc;
tagInfoPtr->numTags++;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeCheck --
*
* This function 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 function panics with an error
* message.
*
*----------------------------------------------------------------------
*/
void
TkBTreeCheck(
TkTextBTree tree) /* Tree to check. */
{
BTree *treePtr = (BTree *) tree;
Summary *summaryPtr;
Node *nodePtr;
TkTextLine *linePtr;
TkTextSegment *segPtr;
TkTextTag *tagPtr;
Tcl_HashEntry *entryPtr;
Tcl_HashSearch search;
int count;
/*
* Make sure that the tag toggle counts and the tag root pointers are OK.
*/
for (entryPtr=Tcl_FirstHashEntry(&treePtr->sharedTextPtr->tagTable,&search);
entryPtr != NULL ; entryPtr = Tcl_NextHashEntry(&search)) {
tagPtr = (TkTextTag *)Tcl_GetHashValue(entryPtr);
nodePtr = tagPtr->tagRootPtr;
if (nodePtr == NULL) {
if (tagPtr->toggleCount != 0) {
Tcl_Panic("TkBTreeCheck found \"%s\" with toggles (%d) but no root",
tagPtr->name, tagPtr->toggleCount);
}
continue; /* No ranges for the tag. */
} else if (tagPtr->toggleCount == 0) {
Tcl_Panic("TkBTreeCheck found root for \"%s\" with no toggles",
tagPtr->name);
} else if (tagPtr->toggleCount & 1) {
Tcl_Panic("TkBTreeCheck found odd toggle count for \"%s\" (%d)",
tagPtr->name, tagPtr->toggleCount);
}
for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr->tagPtr == tagPtr) {
Tcl_Panic("TkBTreeCheck found root node with summary info");
}
}
count = 0;
if (nodePtr->level > 0) {
for (nodePtr = nodePtr->children.nodePtr ; nodePtr != NULL ;
nodePtr = nodePtr->nextPtr) {
for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr->tagPtr == tagPtr) {
count += summaryPtr->toggleCount;
}
}
}
} else {
for (linePtr = nodePtr->children.linePtr ; linePtr != NULL ;
linePtr = linePtr->nextPtr) {
for (segPtr = linePtr->segPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
if ((segPtr->typePtr == &tkTextToggleOnType ||
segPtr->typePtr == &tkTextToggleOffType) &&
segPtr->body.toggle.tagPtr == tagPtr) {
count++;
}
}
}
}
if (count != tagPtr->toggleCount) {
Tcl_Panic("TkBTreeCheck toggleCount (%d) wrong for \"%s\" should be (%d)",
tagPtr->toggleCount, tagPtr->name, count);
}
}
/*
* Call a recursive function to do the main body of checks.
*/
nodePtr = treePtr->rootPtr;
CheckNodeConsistency(treePtr->rootPtr, treePtr->pixelReferences);
/*
* Make sure that there are at least two lines in the text and that the
* last line has no characters except a newline.
*/
if (nodePtr->numLines < 2) {
Tcl_Panic("TkBTreeCheck: less than 2 lines in tree");
}
while (nodePtr->level > 0) {
nodePtr = nodePtr->children.nodePtr;
while (nodePtr->nextPtr != NULL) {
nodePtr = nodePtr->nextPtr;
}
}
linePtr = nodePtr->children.linePtr;
while (linePtr->nextPtr != NULL) {
linePtr = linePtr->nextPtr;
}
segPtr = linePtr->segPtr;
while ((segPtr->typePtr == &tkTextToggleOffType)
|| (segPtr->typePtr == &tkTextRightMarkType)
|| (segPtr->typePtr == &tkTextLeftMarkType)) {
/*
* It's OK to toggle a tag off in the last line, but not to start a
* new range. It's also OK to have marks in the last line.
*/
segPtr = segPtr->nextPtr;
}
if (segPtr->typePtr != &tkTextCharType) {
Tcl_Panic("TkBTreeCheck: last line has bogus segment type");
}
if (segPtr->nextPtr != NULL) {
Tcl_Panic("TkBTreeCheck: last line has too many segments");
}
if (segPtr->size != 1) {
Tcl_Panic("TkBTreeCheck: last line has wrong # characters: %d",
segPtr->size);
}
if ((segPtr->body.chars[0] != '\n') || (segPtr->body.chars[1] != 0)) {
Tcl_Panic("TkBTreeCheck: last line had bad value: %s",
segPtr->body.chars);
}
}
/*
*----------------------------------------------------------------------
*
* CheckNodeConsistency --
*
* This function 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 function
* panics.
*
*----------------------------------------------------------------------
*/
static void
CheckNodeConsistency(
Node *nodePtr, /* Node whose subtree should be checked. */
int references) /* Number of referring widgets which have
* pixel counts. */
{
Node *childNodePtr;
Summary *summaryPtr, *summaryPtr2;
TkTextLine *linePtr;
TkTextSegment *segPtr;
int numChildren, numLines, toggleCount, minChildren, i;
int *numPixels;
int pixels[PIXEL_CLIENTS];
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)) {
Tcl_Panic("CheckNodeConsistency: bad child count (%d)",
nodePtr->numChildren);
}
numChildren = 0;
numLines = 0;
if (references > PIXEL_CLIENTS) {
numPixels = (int *)ckalloc(sizeof(int) * references);
} else {
numPixels = pixels;
}
for (i = 0; i<references; i++) {
numPixels[i] = 0;
}
if (nodePtr->level == 0) {
for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
linePtr = linePtr->nextPtr) {
if (linePtr->parentPtr != nodePtr) {
Tcl_Panic("CheckNodeConsistency: line doesn't point to parent");
}
if (linePtr->segPtr == NULL) {
Tcl_Panic("CheckNodeConsistency: line has no segments");
}
for (segPtr = linePtr->segPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
if (segPtr->typePtr->checkProc != NULL) {
segPtr->typePtr->checkProc(segPtr, linePtr);
}
if ((segPtr->size == 0) && (!segPtr->typePtr->leftGravity)
&& (segPtr->nextPtr != NULL)
&& (segPtr->nextPtr->size == 0)
&& (segPtr->nextPtr->typePtr->leftGravity)) {
Tcl_Panic("CheckNodeConsistency: wrong segment order for gravity");
}
if ((segPtr->nextPtr == NULL)
&& (segPtr->typePtr != &tkTextCharType)) {
Tcl_Panic("CheckNodeConsistency: line ended with wrong type");
}
}
numChildren++;
numLines++;
for (i = 0; i<references; i++) {
numPixels[i] += linePtr->pixels[2 * i];
}
}
} else {
for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL;
childNodePtr = childNodePtr->nextPtr) {
if (childNodePtr->parentPtr != nodePtr) {
Tcl_Panic("CheckNodeConsistency: node doesn't point to parent");
}
if (childNodePtr->level != (nodePtr->level-1)) {
Tcl_Panic("CheckNodeConsistency: level mismatch (%d %d)",
nodePtr->level, childNodePtr->level);
}
CheckNodeConsistency(childNodePtr, references);
for (summaryPtr = childNodePtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
for (summaryPtr2 = nodePtr->summaryPtr; ;
summaryPtr2 = summaryPtr2->nextPtr) {
if (summaryPtr2 == NULL) {
if (summaryPtr->tagPtr->tagRootPtr == nodePtr) {
break;
}
Tcl_Panic("CheckNodeConsistency: node tag \"%s\" not %s",
summaryPtr->tagPtr->name,
"present in parent summaries");
}
if (summaryPtr->tagPtr == summaryPtr2->tagPtr) {
break;
}
}
}
numChildren++;
numLines += childNodePtr->numLines;
for (i = 0; i<references; i++) {
numPixels[i] += childNodePtr->numPixels[i];
}
}
}
if (numChildren != nodePtr->numChildren) {
Tcl_Panic("CheckNodeConsistency: mismatch in numChildren (%d %d)",
numChildren, nodePtr->numChildren);
}
if (numLines != nodePtr->numLines) {
Tcl_Panic("CheckNodeConsistency: mismatch in numLines (%d %d)",
numLines, nodePtr->numLines);
}
for (i = 0; i<references; i++) {
if (numPixels[i] != nodePtr->numPixels[i]) {
Tcl_Panic("CheckNodeConsistency: mismatch in numPixels (%d %d) for widget (%d)",
numPixels[i], nodePtr->numPixels[i], i);
}
}
if (references > PIXEL_CLIENTS) {
ckfree(numPixels);
}
for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr->tagPtr->toggleCount == summaryPtr->toggleCount) {
Tcl_Panic("CheckNodeConsistency: found unpruned root for \"%s\"",
summaryPtr->tagPtr->name);
}
toggleCount = 0;
if (nodePtr->level == 0) {
for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
linePtr = linePtr->nextPtr) {
for (segPtr = linePtr->segPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
if ((segPtr->typePtr != &tkTextToggleOnType)
&& (segPtr->typePtr != &tkTextToggleOffType)) {
continue;
}
if (segPtr->body.toggle.tagPtr == summaryPtr->tagPtr) {
toggleCount++;
}
}
}
} else {
for (childNodePtr = nodePtr->children.nodePtr;
childNodePtr != NULL;
childNodePtr = childNodePtr->nextPtr) {
for (summaryPtr2 = childNodePtr->summaryPtr;
summaryPtr2 != NULL;
summaryPtr2 = summaryPtr2->nextPtr) {
if (summaryPtr2->tagPtr == summaryPtr->tagPtr) {
toggleCount += summaryPtr2->toggleCount;
}
}
}
}
if (toggleCount != summaryPtr->toggleCount) {
Tcl_Panic("CheckNodeConsistency: mismatch in toggleCount (%d %d)",
toggleCount, summaryPtr->toggleCount);
}
for (summaryPtr2 = summaryPtr->nextPtr; summaryPtr2 != NULL;
summaryPtr2 = summaryPtr2->nextPtr) {
if (summaryPtr2->tagPtr == summaryPtr->tagPtr) {
Tcl_Panic("CheckNodeConsistency: duplicated node tag: %s",
summaryPtr->tagPtr->name);
}
}
}
}
/*
*----------------------------------------------------------------------
*
* Rebalance --
*
* This function 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(
BTree *treePtr, /* Tree that is being rebalanced. */
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) {
Node *newPtr, *childPtr;
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;
newPtr->numPixels = (int *)
ckalloc(sizeof(int) * treePtr->pixelReferences);
for (i=0; i<treePtr->pixelReferences; i++) {
newPtr->numPixels[i] = nodePtr->numPixels[i];
}
RecomputeNodeCounts(treePtr, newPtr);
treePtr->rootPtr = newPtr;
}
newPtr = (Node *)ckalloc(sizeof(Node));
newPtr->numPixels = (int *)
ckalloc(sizeof(int) * treePtr->pixelReferences);
for (i=0; i<treePtr->pixelReferences; i++) {
newPtr->numPixels[i] = 0;
}
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(treePtr, nodePtr);
nodePtr->parentPtr->numChildren++;
nodePtr = newPtr;
if (nodePtr->numChildren <= MAX_CHILDREN) {
RecomputeNodeCounts(treePtr, nodePtr);
break;
}
}
}
while (nodePtr->numChildren < MIN_CHILDREN) {
Node *otherPtr;
Node *halfwayNodePtr = NULL; /* Initialization needed only */
TkTextLine *halfwayLinePtr = NULL; /* to prevent cc warnings. */
int totalChildren, firstChildren;
/*
* Too few children for this node. If this is the root then, it's
* OK for it to have less than MIN_CHILDREN children as long as
* it's got at least two. If it has only one (and isn't at level
* 0), then chop the root node out of the tree and use its child
* as the new root.
*/
if (nodePtr->parentPtr == NULL) {
if ((nodePtr->numChildren == 1) && (nodePtr->level > 0)) {
treePtr->rootPtr = nodePtr->children.nodePtr;
treePtr->rootPtr->parentPtr = NULL;
DeleteSummaries(nodePtr->summaryPtr);
ckfree(nodePtr->numPixels);
ckfree(nodePtr);
}
return;
}
/*
* Not the root. Make sure that there are siblings to balance
* with.
*/
if (nodePtr->parentPtr->numChildren < 2) {
Rebalance(treePtr, nodePtr->parentPtr);
continue;
}
/*
* Find a sibling neighbor to borrow from, and arrange for nodePtr
* to be the earlier of the pair.
*/
if (nodePtr->nextPtr == NULL) {
for (otherPtr = nodePtr->parentPtr->children.nodePtr;
otherPtr->nextPtr != nodePtr;
otherPtr = otherPtr->nextPtr) {
/* Empty loop body. */
}
nodePtr = otherPtr;
}
otherPtr = nodePtr->nextPtr;
/*
* We're going to either merge the two siblings together into one
* node or redivide the children among them to balance their
* loads. As preparation, join their two child lists into a single
* list and remember the half-way point in the list.
*/
totalChildren = nodePtr->numChildren + otherPtr->numChildren;
firstChildren = totalChildren/2;
if (nodePtr->children.nodePtr == NULL) {
nodePtr->children = otherPtr->children;
otherPtr->children.nodePtr = NULL;
otherPtr->children.linePtr = NULL;
}
if (nodePtr->level == 0) {
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 {
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(treePtr, nodePtr);
nodePtr->nextPtr = otherPtr->nextPtr;
nodePtr->parentPtr->numChildren--;
DeleteSummaries(otherPtr->summaryPtr);
ckfree(otherPtr->numPixels);
ckfree(otherPtr);
continue;
}
/*
* The siblings can't be merged, so just divide their children
* evenly between them.
*/
if (nodePtr->level == 0) {
CLANG_ASSERT(halfwayLinePtr);
otherPtr->children.linePtr = halfwayLinePtr->nextPtr;
halfwayLinePtr->nextPtr = NULL;
} else {
CLANG_ASSERT(halfwayNodePtr);
otherPtr->children.nodePtr = halfwayNodePtr->nextPtr;
halfwayNodePtr->nextPtr = NULL;
}
RecomputeNodeCounts(treePtr, nodePtr);
RecomputeNodeCounts(treePtr, otherPtr);
}
}
}
/*
*----------------------------------------------------------------------
*
* RecomputeNodeCounts --
*
* This function is called to recompute all the counts in a node (tags,
* child information, etc.) by scanning the information in its
* descendants. This function is called during rebalancing when a node's
* child structure has changed.
*
* Results:
* None.
*
* Side effects:
* The tag counts for nodePtr are modified to reflect its current child
* structure, as are its numChildren and numLines fields. Also, all of
* the childrens' parentPtr fields are made to point to nodePtr.
*
*----------------------------------------------------------------------
*/
static void
RecomputeNodeCounts(
BTree *treePtr, /* The whole B-tree. */
Node *nodePtr) /* Node whose tag summary information must be
* recomputed. */
{
Summary *summaryPtr, *summaryPtr2;
Node *childPtr;
TkTextLine *linePtr;
TkTextSegment *segPtr;
TkTextTag *tagPtr;
int ref;
/*
* 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;
for (ref = 0; ref<treePtr->pixelReferences; ref++) {
nodePtr->numPixels[ref] = 0;
}
/*
* Scan through the children, adding the childrens' tag counts into the
* node's tag counts and adding new Summary structures if necessary.
*/
if (nodePtr->level == 0) {
for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
linePtr = linePtr->nextPtr) {
nodePtr->numChildren++;
nodePtr->numLines++;
for (ref = 0; ref<treePtr->pixelReferences; ref++) {
nodePtr->numPixels[ref] += linePtr->pixels[2 * ref];
}
linePtr->parentPtr = nodePtr;
for (segPtr = linePtr->segPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
if (((segPtr->typePtr != &tkTextToggleOnType)
&& (segPtr->typePtr != &tkTextToggleOffType))
|| !(segPtr->body.toggle.inNodeCounts)) {
continue;
}
tagPtr = segPtr->body.toggle.tagPtr;
for (summaryPtr = nodePtr->summaryPtr; ;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr == NULL) {
summaryPtr = (Summary *)ckalloc(sizeof(Summary));
summaryPtr->tagPtr = tagPtr;
summaryPtr->toggleCount = 1;
summaryPtr->nextPtr = nodePtr->summaryPtr;
nodePtr->summaryPtr = summaryPtr;
break;
}
if (summaryPtr->tagPtr == tagPtr) {
summaryPtr->toggleCount++;
break;
}
}
}
}
} else {
for (childPtr = nodePtr->children.nodePtr; childPtr != NULL;
childPtr = childPtr->nextPtr) {
nodePtr->numChildren++;
nodePtr->numLines += childPtr->numLines;
for (ref = 0; ref<treePtr->pixelReferences; ref++) {
nodePtr->numPixels[ref] += childPtr->numPixels[ref];
}
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, or that have all the toggles.
* The node with the children that account for all the tags toggles have
* no summary information, and they become the tagRootPtr for the tag.
*/
summaryPtr2 = NULL;
for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; ) {
if (summaryPtr->toggleCount > 0 &&
summaryPtr->toggleCount < summaryPtr->tagPtr->toggleCount) {
if (nodePtr->level == summaryPtr->tagPtr->tagRootPtr->level) {
/*
* The tag's root node split and some toggles left. The tag
* root must move up a level.
*/
summaryPtr->tagPtr->tagRootPtr = nodePtr->parentPtr;
}
summaryPtr2 = summaryPtr;
summaryPtr = summaryPtr->nextPtr;
continue;
}
if (summaryPtr->toggleCount == summaryPtr->tagPtr->toggleCount) {
/*
* A node merge has collected all the toggles under one node. Push
* the root down to this level.
*/
summaryPtr->tagPtr->tagRootPtr = nodePtr;
}
if (summaryPtr2 != NULL) {
summaryPtr2->nextPtr = summaryPtr->nextPtr;
ckfree(summaryPtr);
summaryPtr = summaryPtr2->nextPtr;
} else {
nodePtr->summaryPtr = summaryPtr->nextPtr;
ckfree(summaryPtr);
summaryPtr = nodePtr->summaryPtr;
}
}
}
/*
*----------------------------------------------------------------------
*
* TkBTreeNumLines --
*
* This function returns a count of the number of logical lines of text
* present in a given B-tree.
*
* Results:
* The return value is a count of the number of usable lines in tree
* (i.e. it doesn't include the dummy line that is just used to mark the
* end of the tree).
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkBTreeNumLines(
TkTextBTree tree, /* Information about tree. */
const TkText *textPtr) /* Relative to this client of the B-tree. */
{
BTree *treePtr = (BTree *) tree;
int count;
if (textPtr != NULL && textPtr->end != NULL) {
count = TkBTreeLinesTo(NULL, textPtr->end);
} else {
count = treePtr->rootPtr->numLines - 1;
}
if (textPtr != NULL && textPtr->start != NULL) {
count -= TkBTreeLinesTo(NULL, textPtr->start);
}
return count;
}
/*
*----------------------------------------------------------------------
*
* TkBTreeNumPixels --
*
* This function returns a count of the number of pixels of text present
* in a given widget's B-tree representation.
*
* Results:
* The return value is a count of the number of usable pixels in tree
* (since the dummy line used to mark the end of the B-tree is maintained
* with zero height, as are any lines that are before or after the
* '-start -end' range of the text widget in question, the number stored
* at the root is the number we want).
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkBTreeNumPixels(
TkTextBTree tree, /* The B-tree. */
const TkText *textPtr) /* Relative to this client of the B-tree. */
{
BTree *treePtr = (BTree *) tree;
return treePtr->rootPtr->numPixels[textPtr->pixelReference];
}
/*
*--------------------------------------------------------------
*
* CharSplitProc --
*
* This function implements splitting for character segments.
*
* Results:
* The return value is a pointer to a chain of two segments that have the
* same characters as segPtr except split among the two segments.
*
* Side effects:
* Storage for segPtr is freed.
*
*--------------------------------------------------------------
*/
static TkTextSegment *
CharSplitProc(
TkTextSegment *segPtr, /* Pointer to segment to split. */
int index) /* Position within segment at which to
* split. */
{
TkTextSegment *newPtr1, *newPtr2;
newPtr1 = (TkTextSegment *)ckalloc(CSEG_SIZE(index));
newPtr2 = (TkTextSegment *)ckalloc(CSEG_SIZE(segPtr->size - index));
newPtr1->typePtr = &tkTextCharType;
newPtr1->nextPtr = newPtr2;
newPtr1->size = index;
memcpy(newPtr1->body.chars, segPtr->body.chars, index);
newPtr1->body.chars[index] = 0;
newPtr2->typePtr = &tkTextCharType;
newPtr2->nextPtr = segPtr->nextPtr;
newPtr2->size = segPtr->size - index;
memcpy(newPtr2->body.chars, segPtr->body.chars + index, newPtr2->size);
newPtr2->body.chars[newPtr2->size] = 0;
ckfree(segPtr);
return newPtr1;
}
/*
*--------------------------------------------------------------
*
* CharCleanupProc --
*
* This function merges adjacent character segments into a single
* character segment, if possible.
*
* Results:
* The return value is a pointer to the first segment in the (new) list
* of segments that used to start with segPtr.
*
* Side effects:
* Storage for the segments may be allocated and freed.
*
*--------------------------------------------------------------
*/
static TkTextSegment *
CharCleanupProc(
TkTextSegment *segPtr, /* Pointer to first of two adjacent segments
* to join. */
TCL_UNUSED(TkTextLine *)) /* Line containing segments (not used). */
{
TkTextSegment *segPtr2, *newPtr;
segPtr2 = segPtr->nextPtr;
if ((segPtr2 == NULL) || (segPtr2->typePtr != &tkTextCharType)) {
return segPtr;
}
newPtr = (TkTextSegment *)ckalloc(CSEG_SIZE(segPtr->size + segPtr2->size));
newPtr->typePtr = &tkTextCharType;
newPtr->nextPtr = segPtr2->nextPtr;
newPtr->size = segPtr->size + segPtr2->size;
memcpy(newPtr->body.chars, segPtr->body.chars, segPtr->size);
memcpy(newPtr->body.chars + segPtr->size, segPtr2->body.chars, segPtr2->size);
newPtr->body.chars[newPtr->size] = 0;
ckfree(segPtr);
ckfree(segPtr2);
return newPtr;
}
/*
*--------------------------------------------------------------
*
* CharDeleteProc --
*
* This function is invoked to delete a character segment.
*
* Results:
* Always returns 0 to indicate that the segment was deleted.
*
* Side effects:
* Storage for the segment is freed.
*
*--------------------------------------------------------------
*/
static int
CharDeleteProc(
TkTextSegment *segPtr, /* Segment to delete. */
TCL_UNUSED(TkTextLine *), /* Line containing segment. */
TCL_UNUSED(int)) /* Non-zero means the entire tree is being
* deleted, so everything must get cleaned
* up. */
{
ckfree(segPtr);
return 0;
}
/*
*--------------------------------------------------------------
*
* CharCheckProc --
*
* This function is invoked to perform consistency checks on character
* segments.
*
* Results:
* None.
*
* Side effects:
* If the segment isn't inconsistent then the function panics.
*
*--------------------------------------------------------------
*/
static void
CharCheckProc(
TkTextSegment *segPtr, /* Segment to check. */
TCL_UNUSED(TkTextLine *)) /* Line containing segment. */
{
/*
* Make sure that the segment contains the number of characters indicated
* by its header, and that the last segment in a line ends in a newline.
* Also make sure that there aren't ever two character segments adjacent
* to each other: they should be merged together.
*/
if (segPtr->size <= 0) {
Tcl_Panic("CharCheckProc: segment has size <= 0");
}
if (strlen(segPtr->body.chars) != (size_t)segPtr->size) {
Tcl_Panic("CharCheckProc: segment has wrong size");
}
if (segPtr->nextPtr == NULL) {
if (segPtr->body.chars[segPtr->size-1] != '\n') {
Tcl_Panic("CharCheckProc: line doesn't end with newline");
}
} else if (segPtr->nextPtr->typePtr == &tkTextCharType) {
Tcl_Panic("CharCheckProc: adjacent character segments weren't merged");
}
}
/*
*--------------------------------------------------------------
*
* ToggleDeleteProc --
*
* This function is invoked to delete toggle segments.
*
* Results:
* Returns 1 to indicate that the segment may not be deleted, unless the
* entire B-tree is going away.
*
* Side effects:
* If the tree is going away then the toggle's memory is freed; otherwise
* the toggle counts in nodes above the segment get updated.
*
*--------------------------------------------------------------
*/
static int
ToggleDeleteProc(
TkTextSegment *segPtr, /* Segment to check. */
TkTextLine *linePtr, /* Line containing segment. */
int treeGone) /* Non-zero means the entire tree is being
* deleted, so everything must get cleaned
* up. */
{
if (treeGone) {
ckfree(segPtr);
return 0;
}
/*
* This toggle is in the middle of a range of characters that's being
* deleted. Refuse to die. We'll be moved to the end of the deleted range
* and our cleanup function will be called later. Decrement node toggle
* counts here, and set a flag so we'll re-increment them in the cleanup
* function.
*/
if (segPtr->body.toggle.inNodeCounts) {
ChangeNodeToggleCount(linePtr->parentPtr,
segPtr->body.toggle.tagPtr, -1);
segPtr->body.toggle.inNodeCounts = 0;
}
return 1;
}
/*
*--------------------------------------------------------------
*
* ToggleCleanupProc --
*
* This function is called when a toggle is part of a line that's been
* modified in some way. It's invoked after the modifications are
* complete.
*
* Results:
* The return value is the head segment in a new list that is to replace
* the tail of the line that used to start at segPtr. This allows the
* function to delete or modify segPtr.
*
* Side effects:
* Toggle counts in the nodes above the new line will be updated if
* they're not already. Toggles may be collapsed if there are duplicate
* toggles at the same position.
*
*--------------------------------------------------------------
*/
static TkTextSegment *
ToggleCleanupProc(
TkTextSegment *segPtr, /* Segment to check. */
TkTextLine *linePtr) /* Line that now contains segment. */
{
TkTextSegment *segPtr2, *prevPtr;
int counts;
/*
* If this is a toggle-off segment, look ahead through the next segments
* to see if there's a toggle-on segment for the same tag before any
* segments with non-zero size. If so then the two toggles cancel each
* other; remove them both.
*/
if (segPtr->typePtr == &tkTextToggleOffType) {
for (prevPtr = segPtr, segPtr2 = prevPtr->nextPtr;
(segPtr2 != NULL) && (segPtr2->size == 0);
prevPtr = segPtr2, segPtr2 = prevPtr->nextPtr) {
if (segPtr2->typePtr != &tkTextToggleOnType) {
continue;
}
if (segPtr2->body.toggle.tagPtr != segPtr->body.toggle.tagPtr) {
continue;
}
counts = segPtr->body.toggle.inNodeCounts
+ segPtr2->body.toggle.inNodeCounts;
if (counts != 0) {
ChangeNodeToggleCount(linePtr->parentPtr,
segPtr->body.toggle.tagPtr, -counts);
}
prevPtr->nextPtr = segPtr2->nextPtr;
ckfree(segPtr2);
segPtr2 = segPtr->nextPtr;
ckfree(segPtr);
return segPtr2;
}
}
if (!segPtr->body.toggle.inNodeCounts) {
ChangeNodeToggleCount(linePtr->parentPtr,
segPtr->body.toggle.tagPtr, 1);
segPtr->body.toggle.inNodeCounts = 1;
}
return segPtr;
}
/*
*--------------------------------------------------------------
*
* ToggleLineChangeProc --
*
* This function is invoked when a toggle segment is about to move from
* one line to another.
*
* Results:
* None.
*
* Side effects:
* Toggle counts are decremented in the nodes above the line.
*
*--------------------------------------------------------------
*/
static void
ToggleLineChangeProc(
TkTextSegment *segPtr, /* Segment to check. */
TkTextLine *linePtr) /* Line that used to contain segment. */
{
if (segPtr->body.toggle.inNodeCounts) {
ChangeNodeToggleCount(linePtr->parentPtr,
segPtr->body.toggle.tagPtr, -1);
segPtr->body.toggle.inNodeCounts = 0;
}
}
/*
*--------------------------------------------------------------
*
* ToggleCheckProc --
*
* This function is invoked to perform consistency checks on toggle
* segments.
*
* Results:
* None.
*
* Side effects:
* If a consistency problem is found the function panics.
*
*--------------------------------------------------------------
*/
static void
ToggleCheckProc(
TkTextSegment *segPtr, /* Segment to check. */
TkTextLine *linePtr) /* Line containing segment. */
{
Summary *summaryPtr;
int needSummary;
if (segPtr->size != 0) {
Tcl_Panic("ToggleCheckProc: segment had non-zero size");
}
if (!segPtr->body.toggle.inNodeCounts) {
Tcl_Panic("ToggleCheckProc: toggle counts not updated in nodes");
}
needSummary = (segPtr->body.toggle.tagPtr->tagRootPtr!=linePtr->parentPtr);
for (summaryPtr = linePtr->parentPtr->summaryPtr; ;
summaryPtr = summaryPtr->nextPtr) {
if (summaryPtr == NULL) {
if (needSummary) {
Tcl_Panic("ToggleCheckProc: tag not present in node");
} else {
break;
}
}
if (summaryPtr->tagPtr == segPtr->body.toggle.tagPtr) {
if (!needSummary) {
Tcl_Panic("ToggleCheckProc: tag present in root node summary");
}
break;
}
}
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/