/*
* tkTextMark.c --
*
* This file contains the functions that implement marks for text
* widgets.
*
* Copyright (c) 1994 The Regents of the University of California.
* Copyright (c) 1994-1997 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"
#include "tk3d.h"
/*
* Macro that determines the size of a mark segment:
*/
#define MSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \
+ sizeof(TkTextMark)))
/*
* Forward references for functions defined in this file:
*/
static Tcl_Obj * GetMarkName(TkText *textPtr, TkTextSegment *segPtr);
static void InsertUndisplayProc(TkText *textPtr,
TkTextDispChunk *chunkPtr);
static int MarkDeleteProc(TkTextSegment *segPtr,
TkTextLine *linePtr, int treeGone);
static TkTextSegment * MarkCleanupProc(TkTextSegment *segPtr,
TkTextLine *linePtr);
static void MarkCheckProc(TkTextSegment *segPtr,
TkTextLine *linePtr);
static int MarkLayoutProc(TkText *textPtr, TkTextIndex *indexPtr,
TkTextSegment *segPtr, int offset, int maxX,
int maxChars, int noCharsYet, TkWrapMode wrapMode,
TkTextDispChunk *chunkPtr);
static int MarkFindNext(Tcl_Interp *interp,
TkText *textPtr, Tcl_Obj *markName);
static int MarkFindPrev(Tcl_Interp *interp,
TkText *textPtr, Tcl_Obj *markName);
/*
* The following structures declare the "mark" segment types. There are
* actually two types for marks, one with left gravity and one with right
* gravity. They are identical except for their gravity property.
*/
const Tk_SegType tkTextRightMarkType = {
"mark", /* name */
0, /* leftGravity */
NULL, /* splitProc */
MarkDeleteProc, /* deleteProc */
MarkCleanupProc, /* cleanupProc */
NULL, /* lineChangeProc */
MarkLayoutProc, /* layoutProc */
MarkCheckProc /* checkProc */
};
const Tk_SegType tkTextLeftMarkType = {
"mark", /* name */
1, /* leftGravity */
NULL, /* splitProc */
MarkDeleteProc, /* deleteProc */
MarkCleanupProc, /* cleanupProc */
NULL, /* lineChangeProc */
MarkLayoutProc, /* layoutProc */
MarkCheckProc /* checkProc */
};
/*
*--------------------------------------------------------------
*
* TkTextMarkCmd --
*
* This function is invoked to process the "mark" options of the widget
* command for text widgets. See the user documentation for details on
* what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*--------------------------------------------------------------
*/
int
TkTextMarkCmd(
register TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
* parsed this command enough to know that
* objv[1] is "mark". */
{
Tcl_HashEntry *hPtr;
TkTextSegment *markPtr;
Tcl_HashSearch search;
TkTextIndex index;
const Tk_SegType *newTypePtr;
int optionIndex;
static const char *const markOptionStrings[] = {
"gravity", "names", "next", "previous", "set", "unset", NULL
};
enum markOptions {
MARK_GRAVITY, MARK_NAMES, MARK_NEXT, MARK_PREVIOUS, MARK_SET,
MARK_UNSET
};
if (objc < 3) {
Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
return TCL_ERROR;
}
if (Tcl_GetIndexFromObjStruct(interp, objv[2], markOptionStrings,
sizeof(char *), "mark option", 0, &optionIndex) != TCL_OK) {
return TCL_ERROR;
}
switch ((enum markOptions) optionIndex) {
case MARK_GRAVITY: {
char c;
int length;
const char *str;
if (objc < 4 || objc > 5) {
Tcl_WrongNumArgs(interp, 3, objv, "markName ?gravity?");
return TCL_ERROR;
}
str = Tcl_GetStringFromObj(objv[3], &length);
if (length == 6 && !strcmp(str, "insert")) {
markPtr = textPtr->insertMarkPtr;
} else if (length == 7 && !strcmp(str, "current")) {
markPtr = textPtr->currentMarkPtr;
} else {
hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, str);
if (hPtr == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"there is no mark named \"%s\"", str));
Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_MARK", str,
NULL);
return TCL_ERROR;
}
markPtr = Tcl_GetHashValue(hPtr);
}
if (objc == 4) {
const char *typeStr;
if (markPtr->typePtr == &tkTextRightMarkType) {
typeStr = "right";
} else {
typeStr = "left";
}
Tcl_SetObjResult(interp, Tcl_NewStringObj(typeStr, -1));
return TCL_OK;
}
str = Tcl_GetStringFromObj(objv[4],&length);
c = str[0];
if ((c == 'l') && (strncmp(str, "left", (unsigned) length) == 0)) {
newTypePtr = &tkTextLeftMarkType;
} else if ((c == 'r') &&
(strncmp(str, "right", (unsigned) length) == 0)) {
newTypePtr = &tkTextRightMarkType;
} else {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"bad mark gravity \"%s\": must be left or right", str));
Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_GRAVITY", NULL);
return TCL_ERROR;
}
TkTextMarkSegToIndex(textPtr, markPtr, &index);
TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
markPtr->typePtr = newTypePtr;
TkBTreeLinkSegment(markPtr, &index);
break;
}
case MARK_NAMES: {
Tcl_Obj *resultObj;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
resultObj = Tcl_NewObj();
Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
"insert", -1));
Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
"current", -1));
for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->markTable,
&search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr),
-1));
}
Tcl_SetObjResult(interp, resultObj);
break;
}
case MARK_NEXT:
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "index");
return TCL_ERROR;
}
return MarkFindNext(interp, textPtr, objv[3]);
case MARK_PREVIOUS:
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "index");
return TCL_ERROR;
}
return MarkFindPrev(interp, textPtr, objv[3]);
case MARK_SET:
if (objc != 5) {
Tcl_WrongNumArgs(interp, 3, objv, "markName index");
return TCL_ERROR;
}
if (TkTextGetObjIndex(interp, textPtr, objv[4], &index) != TCL_OK) {
return TCL_ERROR;
}
TkTextSetMark(textPtr, Tcl_GetString(objv[3]), &index);
return TCL_OK;
case MARK_UNSET: {
int i;
for (i = 3; i < objc; i++) {
hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable,
Tcl_GetString(objv[i]));
if (hPtr != NULL) {
markPtr = Tcl_GetHashValue(hPtr);
/*
* Special case not needed with peer widgets.
*/
if ((markPtr == textPtr->insertMarkPtr)
|| (markPtr == textPtr->currentMarkPtr)) {
continue;
}
TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
Tcl_DeleteHashEntry(hPtr);
ckfree(markPtr);
}
}
break;
}
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TkTextSetMark --
*
* Set a mark to a particular position, creating a new mark if one
* doesn't already exist.
*
* Results:
* The return value is a pointer to the mark that was just set.
*
* Side effects:
* A new mark is created, or an existing mark is moved.
*
*----------------------------------------------------------------------
*/
TkTextSegment *
TkTextSetMark(
TkText *textPtr, /* Text widget in which to create mark. */
const char *name, /* Name of mark to set. */
TkTextIndex *indexPtr) /* Where to set mark. */
{
Tcl_HashEntry *hPtr = NULL;
TkTextSegment *markPtr;
TkTextIndex insertIndex;
int isNew, widgetSpecific;
if (!strcmp(name, "insert")) {
widgetSpecific = 1;
markPtr = textPtr->insertMarkPtr;
isNew = (markPtr == NULL ? 1 : 0);
} else if (!strcmp(name, "current")) {
widgetSpecific = 2;
markPtr = textPtr->currentMarkPtr;
isNew = (markPtr == NULL ? 1 : 0);
} else {
widgetSpecific = 0;
hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->markTable, name,
&isNew);
markPtr = Tcl_GetHashValue(hPtr);
}
if (!isNew) {
/*
* If this is the insertion point that's being moved, be sure to force
* a display update at the old position. Also, don't let the insertion
* cursor be after the final newline of the file.
*/
if (markPtr == textPtr->insertMarkPtr) {
TkTextIndex index, index2;
int nblines;
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
/*
* While we wish to redisplay, no heights have changed, so no need
* to call TkTextInvalidateLineMetrics.
*/
TkTextChanged(NULL, textPtr, &index, &index2);
/*
* The number of lines in the widget is zero if and only if it is
* a partial peer with -startline == -endline, i.e. an empty
* peer. In this case the mark shall be set exactly at the given
* index, and not one character backwards (bug 3487407).
*/
nblines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
if ((TkBTreeLinesTo(textPtr, indexPtr->linePtr) == nblines)
&& (nblines > 0)) {
TkTextIndexBackChars(NULL,indexPtr, 1, &insertIndex,
COUNT_INDICES);
indexPtr = &insertIndex;
}
}
TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
} else {
markPtr = ckalloc(MSEG_SIZE);
markPtr->typePtr = &tkTextRightMarkType;
markPtr->size = 0;
markPtr->body.mark.textPtr = textPtr;
markPtr->body.mark.linePtr = indexPtr->linePtr;
markPtr->body.mark.hPtr = hPtr;
if (widgetSpecific == 0) {
Tcl_SetHashValue(hPtr, markPtr);
} else if (widgetSpecific == 1) {
textPtr->insertMarkPtr = markPtr;
} else {
textPtr->currentMarkPtr = markPtr;
}
}
TkBTreeLinkSegment(markPtr, indexPtr);
/*
* If the mark is the insertion cursor, then update the screen at the
* mark's new location.
*/
if (markPtr == textPtr->insertMarkPtr) {
TkTextIndex index2;
TkTextIndexForwChars(NULL, indexPtr, 1, &index2, COUNT_INDICES);
/*
* While we wish to redisplay, no heights have changed, so no need to
* call TkTextInvalidateLineMetrics
*/
TkTextChanged(NULL, textPtr, indexPtr, &index2);
}
return markPtr;
}
/*
*--------------------------------------------------------------
*
* TkTextMarkSegToIndex --
*
* Given a segment that is a mark, create an index that refers to the
* next text character (or other text segment with non-zero size) after
* the mark.
*
* Results:
* *IndexPtr is filled in with index information.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
TkTextMarkSegToIndex(
TkText *textPtr, /* Text widget containing mark. */
TkTextSegment *markPtr, /* Mark segment. */
TkTextIndex *indexPtr) /* Index information gets stored here. */
{
TkTextSegment *segPtr;
indexPtr->tree = textPtr->sharedTextPtr->tree;
indexPtr->linePtr = markPtr->body.mark.linePtr;
indexPtr->byteIndex = 0;
for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr;
segPtr = segPtr->nextPtr) {
indexPtr->byteIndex += segPtr->size;
}
}
/*
*--------------------------------------------------------------
*
* TkTextMarkNameToIndex --
*
* Given the name of a mark, return an index corresponding to the mark
* name.
*
* Results:
* The return value is TCL_OK if "name" exists as a mark in the text
* widget and is located within its -starline/-endline range. In this
* case *indexPtr is filled in with the next segment who is after the
* mark whose size is non-zero. TCL_ERROR is returned if the mark
* doesn't exist in the text widget, or if it is out of its -starline/
* -endline range. In this latter case *indexPtr still contains valid
* information, in particular TkTextMarkNameToIndex called with the
* "insert" or "current" mark name may return TCL_ERROR, but *indexPtr
* contains the correct index of this mark before -startline or after
* -endline.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
TkTextMarkNameToIndex(
TkText *textPtr, /* Text widget containing mark. */
const char *name, /* Name of mark. */
TkTextIndex *indexPtr) /* Index information gets stored here. */
{
TkTextSegment *segPtr;
TkTextIndex index;
int start, end;
if (textPtr == NULL) {
return TCL_ERROR;
}
if (!strcmp(name, "insert")) {
segPtr = textPtr->insertMarkPtr;
} else if (!strcmp(name, "current")) {
segPtr = textPtr->currentMarkPtr;
} else {
Tcl_HashEntry *hPtr =
Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name);
if (hPtr == NULL) {
return TCL_ERROR;
}
segPtr = Tcl_GetHashValue(hPtr);
}
TkTextMarkSegToIndex(textPtr, segPtr, indexPtr);
/* If indexPtr refers to somewhere outside the -startline/-endline
* range limits of the widget, error out since the mark indeed is not
* reachable from this text widget (it may be reachable from a peer)
* (bug 1630271).
*/
if (textPtr->start != NULL) {
start = TkBTreeLinesTo(NULL, textPtr->start);
TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, start, 0,
&index);
if (TkTextIndexCmp(indexPtr, &index) < 0) {
return TCL_ERROR;
}
}
if (textPtr->end != NULL) {
end = TkBTreeLinesTo(NULL, textPtr->end);
TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, end, 0,
&index);
if (TkTextIndexCmp(indexPtr, &index) > 0) {
return TCL_ERROR;
}
}
return TCL_OK;
}
/*
*--------------------------------------------------------------
*
* MarkDeleteProc --
*
* This function is invoked by the text B-tree code whenever a mark lies
* in a range of characters being deleted.
*
* Results:
* Returns 1 to indicate that deletion has been rejected.
*
* Side effects:
* None (even if the whole tree is being deleted we don't free up the
* mark; it will be done elsewhere).
*
*--------------------------------------------------------------
*/
/* ARGSUSED */
static int
MarkDeleteProc(
TkTextSegment *segPtr, /* Segment being deleted. */
TkTextLine *linePtr, /* Line containing segment. */
int treeGone) /* Non-zero means the entire tree is being
* deleted, so everything must get cleaned
* up. */
{
return 1;
}
/*
*--------------------------------------------------------------
*
* MarkCleanupProc --
*
* This function is invoked by the B-tree code whenever a mark segment is
* moved from one line to another.
*
* Results:
* None.
*
* Side effects:
* The linePtr field of the segment gets updated.
*
*--------------------------------------------------------------
*/
static TkTextSegment *
MarkCleanupProc(
TkTextSegment *markPtr, /* Mark segment that's being moved. */
TkTextLine *linePtr) /* Line that now contains segment. */
{
markPtr->body.mark.linePtr = linePtr;
return markPtr;
}
/*
*--------------------------------------------------------------
*
* MarkLayoutProc --
*
* This function is the "layoutProc" for mark segments.
*
* Results:
* If the mark isn't the insertion cursor then the return value is -1 to
* indicate that this segment shouldn't be displayed. If the mark is the
* insertion character then 1 is returned and the chunkPtr structure is
* filled in.
*
* Side effects:
* None, except for filling in chunkPtr.
*
*--------------------------------------------------------------
*/
static int
MarkLayoutProc(
TkText *textPtr, /* Text widget being layed out. */
TkTextIndex *indexPtr, /* Identifies first character in chunk. */
TkTextSegment *segPtr, /* Segment corresponding to indexPtr. */
int offset, /* Offset within segPtr corresponding to
* indexPtr (always 0). */
int maxX, /* Chunk must not occupy pixels at this
* position or higher. */
int maxChars, /* Chunk must not include more than this many
* characters. */
int noCharsYet, /* Non-zero means no characters have been
* assigned to this line yet. */
TkWrapMode wrapMode, /* Not used. */
register TkTextDispChunk *chunkPtr)
/* Structure to fill in with information about
* this chunk. The x field has already been
* set by the caller. */
{
if (segPtr != textPtr->insertMarkPtr) {
return -1;
}
chunkPtr->displayProc = TkTextInsertDisplayProc;
chunkPtr->undisplayProc = InsertUndisplayProc;
chunkPtr->measureProc = NULL;
chunkPtr->bboxProc = NULL;
chunkPtr->numBytes = 0;
chunkPtr->minAscent = 0;
chunkPtr->minDescent = 0;
chunkPtr->minHeight = 0;
chunkPtr->width = 0;
/*
* Note: can't break a line after the insertion cursor: this prevents the
* insertion cursor from being stranded at the end of a line.
*/
chunkPtr->breakIndex = -1;
chunkPtr->clientData = textPtr;
return 1;
}
/*
*--------------------------------------------------------------
*
* TkTextInsertDisplayProc --
*
* This function is called to display the insertion cursor.
*
* Results:
* None.
*
* Side effects:
* Graphics are drawn.
*
*--------------------------------------------------------------
*/
/* ARGSUSED */
void
TkTextInsertDisplayProc(
TkText *textPtr, /* The current text widget. */
TkTextDispChunk *chunkPtr, /* Chunk that is to be drawn. */
int x, /* X-position in dst at which to draw this
* chunk (may differ from the x-position in
* the chunk because of scrolling). */
int y, /* Y-position at which to draw this chunk in
* dst (x-position is in the chunk itself). */
int height, /* Total height of line. */
int baseline, /* Offset of baseline from y. */
Display *display, /* Display to use for drawing. */
Drawable dst, /* Pixmap or window in which to draw chunk. */
int screenY) /* Y-coordinate in text window that
* corresponds to y. */
{
/*
* We have no need for the clientData.
*/
/* TkText *textPtr = chunkPtr->clientData; */
TkTextIndex index;
int halfWidth = textPtr->insertWidth/2;
int rightSideWidth;
int ix = 0, iy = 0, iw = 0, ih = 0, charWidth = 0;
if (textPtr->insertCursorType) {
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
TkTextIndexBbox(textPtr, &index, &ix, &iy, &iw, &ih, &charWidth);
rightSideWidth = charWidth + halfWidth;
} else {
rightSideWidth = halfWidth;
}
if ((x + rightSideWidth) < 0) {
/*
* The insertion cursor is off-screen. Indicate caret at 0,0 and
* return.
*/
Tk_SetCaretPos(textPtr->tkwin, 0, 0, height);
return;
}
Tk_SetCaretPos(textPtr->tkwin, x - halfWidth, screenY, height);
/*
* As a special hack to keep the cursor visible on mono displays (or
* anywhere else that the selection and insertion cursors have the same
* color) write the default background in the cursor area (instead of
* nothing) when the cursor isn't on. Otherwise the selection might hide
* the cursor.
*/
if (textPtr->flags & GOT_FOCUS) {
if (textPtr->flags & INSERT_ON) {
Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
x - halfWidth, y, charWidth + textPtr->insertWidth,
height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
} else if (textPtr->selBorder == textPtr->insertBorder) {
Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border,
x - halfWidth, y, charWidth + textPtr->insertWidth,
height, 0, TK_RELIEF_FLAT);
}
} else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_HOLLOW) {
if (textPtr->insertBorderWidth < 1) {
/*
* Hack to work around the fact that a "solid" border always
* paints in black.
*/
TkBorder *borderPtr = (TkBorder *) textPtr->insertBorder;
XDrawRectangle(Tk_Display(textPtr->tkwin), dst, borderPtr->bgGC,
x - halfWidth, y, charWidth + textPtr->insertWidth - 1,
height - 1);
} else {
Tk_Draw3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
x - halfWidth, y, charWidth + textPtr->insertWidth,
height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
}
} else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_SOLID) {
Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
x - halfWidth, y, charWidth + textPtr->insertWidth, height,
textPtr->insertBorderWidth, TK_RELIEF_RAISED);
}
}
/*
*--------------------------------------------------------------
*
* InsertUndisplayProc --
*
* This function is called when the insertion cursor is no longer at a
* visible point on the display. It does nothing right now.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
/* ARGSUSED */
static void
InsertUndisplayProc(
TkText *textPtr, /* Overall information about text widget. */
TkTextDispChunk *chunkPtr) /* Chunk that is about to be freed. */
{
return;
}
/*
*--------------------------------------------------------------
*
* MarkCheckProc --
*
* This function is invoked by the B-tree code to perform consistency
* checks on mark segments.
*
* Results:
* None.
*
* Side effects:
* The function panics if it detects anything wrong with
* the mark.
*
*--------------------------------------------------------------
*/
static void
MarkCheckProc(
TkTextSegment *markPtr, /* Segment to check. */
TkTextLine *linePtr) /* Line containing segment. */
{
Tcl_HashSearch search;
Tcl_HashEntry *hPtr;
if (markPtr->body.mark.linePtr != linePtr) {
Tcl_Panic("MarkCheckProc: markPtr->body.mark.linePtr bogus");
}
/*
* These two marks are not in the hash table
*/
if (markPtr->body.mark.textPtr->insertMarkPtr == markPtr) {
return;
}
if (markPtr->body.mark.textPtr->currentMarkPtr == markPtr) {
return;
}
/*
* Make sure that the mark is still present in the text's mark hash table.
*/
for (hPtr = Tcl_FirstHashEntry(
&markPtr->body.mark.textPtr->sharedTextPtr->markTable,
&search); hPtr != markPtr->body.mark.hPtr;
hPtr = Tcl_NextHashEntry(&search)) {
if (hPtr == NULL) {
Tcl_Panic("MarkCheckProc couldn't find hash table entry for mark");
}
}
}
/*
*--------------------------------------------------------------
*
* MarkFindNext --
*
* This function searches forward for the next mark.
*
* Results:
* A standard Tcl result, which is a mark name or an empty string.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
MarkFindNext(
Tcl_Interp *interp, /* For error reporting */
TkText *textPtr, /* The widget */
Tcl_Obj *obj) /* The starting index or mark name */
{
TkTextIndex index;
Tcl_HashEntry *hPtr;
register TkTextSegment *segPtr;
int offset;
const char *string = Tcl_GetString(obj);
if (!strcmp(string, "insert")) {
segPtr = textPtr->insertMarkPtr;
TkTextMarkSegToIndex(textPtr, segPtr, &index);
segPtr = segPtr->nextPtr;
} else if (!strcmp(string, "current")) {
segPtr = textPtr->currentMarkPtr;
TkTextMarkSegToIndex(textPtr, segPtr, &index);
segPtr = segPtr->nextPtr;
} else {
hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string);
if (hPtr != NULL) {
/*
* If given a mark name, return the next mark in the list of
* segments, even if it happens to be at the same character
* position.
*/
segPtr = Tcl_GetHashValue(hPtr);
TkTextMarkSegToIndex(textPtr, segPtr, &index);
segPtr = segPtr->nextPtr;
} else {
/*
* For non-mark name indices we want to return any marks that are
* right at the index.
*/
if (TkTextGetObjIndex(interp, textPtr, obj, &index) != TCL_OK) {
return TCL_ERROR;
}
for (offset = 0, segPtr = index.linePtr->segPtr;
segPtr != NULL && offset < index.byteIndex;
offset += segPtr->size, segPtr = segPtr->nextPtr) {
/* Empty loop body */ ;
}
}
}
while (1) {
/*
* segPtr points at the first possible candidate, or NULL if we ran
* off the end of the line.
*/
for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) {
if (segPtr->typePtr == &tkTextRightMarkType ||
segPtr->typePtr == &tkTextLeftMarkType) {
Tcl_Obj *markName = GetMarkName(textPtr, segPtr);
if (markName != NULL) {
Tcl_SetObjResult(interp, markName);
return TCL_OK;
}
}
}
index.linePtr = TkBTreeNextLine(textPtr, index.linePtr);
if (index.linePtr == NULL) {
return TCL_OK;
}
index.byteIndex = 0;
segPtr = index.linePtr->segPtr;
}
}
/*
*--------------------------------------------------------------
*
* MarkFindPrev --
*
* This function searches backwards for the previous mark.
*
* Results:
* A standard Tcl result, which is a mark name or an empty string.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
MarkFindPrev(
Tcl_Interp *interp, /* For error reporting */
TkText *textPtr, /* The widget */
Tcl_Obj *obj) /* The starting index or mark name */
{
TkTextIndex index;
Tcl_HashEntry *hPtr;
register TkTextSegment *segPtr, *seg2Ptr, *prevPtr;
int offset;
const char *string = Tcl_GetString(obj);
if (!strcmp(string, "insert")) {
segPtr = textPtr->insertMarkPtr;
TkTextMarkSegToIndex(textPtr, segPtr, &index);
} else if (!strcmp(string, "current")) {
segPtr = textPtr->currentMarkPtr;
TkTextMarkSegToIndex(textPtr, segPtr, &index);
} else {
hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string);
if (hPtr != NULL) {
/*
* If given a mark name, return the previous mark in the list of
* segments, even if it happens to be at the same character
* position.
*/
segPtr = Tcl_GetHashValue(hPtr);
TkTextMarkSegToIndex(textPtr, segPtr, &index);
} else {
/*
* For non-mark name indices we do not return any marks that are
* right at the index.
*/
if (TkTextGetObjIndex(interp, textPtr, obj, &index) != TCL_OK) {
return TCL_ERROR;
}
for (offset = 0, segPtr = index.linePtr->segPtr;
segPtr != NULL && offset < index.byteIndex;
offset += segPtr->size, segPtr = segPtr->nextPtr) {
/* Empty loop body */
}
}
}
while (1) {
/*
* segPtr points just past the first possible candidate, or at the
* beginning of the line.
*/
for (prevPtr = NULL, seg2Ptr = index.linePtr->segPtr;
seg2Ptr != NULL && seg2Ptr != segPtr;
seg2Ptr = seg2Ptr->nextPtr) {
if (seg2Ptr->typePtr == &tkTextRightMarkType ||
seg2Ptr->typePtr == &tkTextLeftMarkType) {
if (seg2Ptr->body.mark.hPtr == NULL) {
if (seg2Ptr != textPtr->currentMarkPtr &&
seg2Ptr != textPtr->insertMarkPtr) {
/*
* This is an insert or current mark from a
* peer of textPtr.
*/
continue;
}
}
prevPtr = seg2Ptr;
}
}
if (prevPtr != NULL) {
Tcl_Obj *markName = GetMarkName(textPtr, prevPtr);
if (markName != NULL) {
Tcl_SetObjResult(interp, markName);
return TCL_OK;
}
}
index.linePtr = TkBTreePreviousLine(textPtr, index.linePtr);
if (index.linePtr == NULL) {
return TCL_OK;
}
segPtr = NULL;
}
}
/*
* ------------------------------------------------------------------------
*
* GetMarkName --
* Returns the name of the mark that is the given text segment, or NULL
* if it is unnamed (i.e., a widget-specific mark that isn't "current" or
* "insert").
*
* ------------------------------------------------------------------------
*/
static Tcl_Obj *
GetMarkName(
TkText *textPtr,
TkTextSegment *segPtr)
{
const char *markName;
if (segPtr == textPtr->currentMarkPtr) {
markName = "current";
} else if (segPtr == textPtr->insertMarkPtr) {
markName = "insert";
} else if (segPtr->body.mark.hPtr == NULL) {
/*
* Ignore widget-specific marks for the other widgets. This is either
* an insert or a current mark (markPtr->body.mark.hPtr actually
* receives NULL for these marks in TkTextSetMark). The insert and
* current marks for textPtr having already been tested above, the
* current segment is an insert or current mark from a peer of
* textPtr, which we don't want to return.
*/
return NULL;
} else {
markName = Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable,
segPtr->body.mark.hPtr);
}
return Tcl_NewStringObj(markName, -1);
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/