/*
*--------------------------------------------------------------
*
* TextWidgetObjCmd --
*
* This procedure is invoked to process the Tcl command
* that corresponds to a text widget. See the user
* documentation for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*--------------------------------------------------------------
*/
static int
TextWidgetObjCmd(clientData, interp, objc, objv)
ClientData clientData; /* Information about text widget. */
Tcl_Interp *interp; /* Current interpreter. */
int objc; /* Number of arguments. */
Tcl_Obj *CONST objv[]; /* Argument objects. */
{
register TkText *textPtr = (TkText *) clientData;
int result = TCL_OK;
int index;
static CONST char *optionStrings[] = {
"bbox", "cget", "compare", "configure", "debug", "delete",
"dlineinfo", "dump", "edit", "get", "image", "index",
"insert", "mark", "scan", "search", "see", "tag",
"window", "xview", "yview", (char *) NULL
};
enum options {
TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_DEBUG,
TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT, TEXT_GET,
TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK, TEXT_SCAN,
TEXT_SEARCH, TEXT_SEE, TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
};
if (objc < 2) {
Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
return TCL_ERROR;
}
if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0,
&index) != TCL_OK) {
return TCL_ERROR;
}
Tcl_Preserve((ClientData) textPtr);
switch ((enum options) index) {
case TEXT_BBOX: {
int x, y, width, height;
CONST TkTextIndex *indexPtr;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index");
result = TCL_ERROR;
goto done;
}
indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
if (indexPtr == NULL) {
result = TCL_ERROR;
goto done;
}
if (TkTextCharBbox(textPtr, indexPtr, &x, &y,
&width, &height) == 0) {
char buf[TCL_INTEGER_SPACE * 4];
sprintf(buf, "%d %d %d %d", x, y, width, height);
Tcl_SetResult(interp, buf, TCL_VOLATILE);
}
break;
}
case TEXT_CGET: {
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "option");
result = TCL_ERROR;
goto done;
} else {
Tcl_Obj *objPtr = Tk_GetOptionValue(interp, (char *) textPtr,
textPtr->optionTable, objv[2], textPtr->tkwin);
if (objPtr == NULL) {
result = TCL_ERROR;
goto done;
} else {
Tcl_SetObjResult(interp, objPtr);
result = TCL_OK;
}
}
break;
}
case TEXT_COMPARE: {
int relation, value;
CONST char *p;
CONST TkTextIndex *index1Ptr, *index2Ptr;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 2, objv, "index1 op index2");
result = TCL_ERROR;
goto done;
}
index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
index2Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[4]);
if (index1Ptr == NULL || index2Ptr == NULL) {
result = TCL_ERROR;
goto done;
}
relation = TkTextIndexCmp(index1Ptr, index2Ptr);
p = Tcl_GetString(objv[3]);
if (p[0] == '<') {
value = (relation < 0);
if ((p[1] == '=') && (p[2] == 0)) {
value = (relation <= 0);
} else if (p[1] != 0) {
compareError:
Tcl_AppendResult(interp, "bad comparison operator \"",
Tcl_GetString(objv[3]),
"\": must be <, <=, ==, >=, >, or !=",
(char *) NULL);
result = TCL_ERROR;
goto done;
}
} else if (p[0] == '>') {
value = (relation > 0);
if ((p[1] == '=') && (p[2] == 0)) {
value = (relation >= 0);
} else if (p[1] != 0) {
goto compareError;
}
} else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
value = (relation == 0);
} else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
value = (relation != 0);
} else {
goto compareError;
}
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
break;
}
case TEXT_CONFIGURE: {
if (objc <= 3) {
Tcl_Obj* objPtr = Tk_GetOptionInfo(interp, (char *) textPtr,
textPtr->optionTable,
(objc == 3) ? objv[2] : (Tcl_Obj *) NULL,
textPtr->tkwin);
if (objPtr == NULL) {
result = TCL_ERROR;
goto done;
} else {
Tcl_SetObjResult(interp, objPtr);
}
} else {
result = ConfigureText(interp, textPtr, objc-2, objv+2);
}
break;
}
case TEXT_DEBUG: {
if (objc > 3) {
Tcl_WrongNumArgs(interp, 2, objv, "boolean");
result = TCL_ERROR;
goto done;
}
if (objc == 2) {
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(tkBTreeDebug));
} else {
if (Tcl_GetBooleanFromObj(interp, objv[2],
&tkBTreeDebug) != TCL_OK) {
result = TCL_ERROR;
goto done;
}
tkTextDebug = tkBTreeDebug;
}
break;
}
case TEXT_DELETE: {
if (objc < 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
result = TCL_ERROR;
goto done;
}
if (textPtr->state == TK_TEXT_STATE_NORMAL) {
if (objc < 5) {
/*
* Simple case requires no predetermination of indices.
*/
result = DeleteChars(textPtr, objv[2],
(objc == 4) ? objv[3] : NULL, NULL, NULL);
} else {
int i;
/*
* Multi-index pair case requires that we prevalidate
* the indices and sort from last to first so that
* deletes occur in the exact (unshifted) text. It
* also needs to handle partial and fully overlapping
* ranges. We have to do this with multiple passes.
*/
TkTextIndex *indices, *ixStart, *ixEnd, *lastStart;
char *useIdx;
objc -= 2;
objv += 2;
indices = (TkTextIndex *)
ckalloc((objc + 1) * sizeof(TkTextIndex));
/*
* First pass verifies that all indices are valid.
*/
for (i = 0; i < objc; i++) {
CONST TkTextIndex *indexPtr =
TkTextGetIndexFromObj(interp, textPtr, objv[i]);
if (indexPtr == NULL) {
result = TCL_ERROR;
ckfree((char *) indices);
goto done;
}
indices[i] = *indexPtr;
}
/*
* Pad out the pairs evenly to make later code easier.
*/
if (objc & 1) {
indices[i] = indices[i-1];
TkTextIndexForwChars(&indices[i], 1, &indices[i]);
objc++;
}
useIdx = (char *) ckalloc((unsigned) objc);
memset(useIdx, 0, (unsigned) objc);
/*
* Do a decreasing order sort so that we delete the end
* ranges first to maintain index consistency.
*/
qsort((VOID *) indices, (unsigned) (objc / 2),
2 * sizeof(TkTextIndex), TextIndexSortProc);
lastStart = NULL;
/*
* Second pass will handle bogus ranges (end < start) and
* overlapping ranges.
*/
for (i = 0; i < objc; i += 2) {
ixStart = &indices[i];
ixEnd = &indices[i+1];
if (TkTextIndexCmp(ixEnd, ixStart) <= 0) {
continue;
}
if (lastStart) {
if (TkTextIndexCmp(ixStart, lastStart) == 0) {
/*
* Start indices were equal, and the sort
* placed the longest range first, so
* skip this one.
*/
continue;
} else if (TkTextIndexCmp(lastStart, ixEnd) < 0) {
/*
* The next pair has a start range before
* the end point of the last range.
* Constrain the delete range, but use
* the pointer values.
*/
*ixEnd = *lastStart;
if (TkTextIndexCmp(ixEnd, ixStart) <= 0) {
continue;
}
}
}
lastStart = ixStart;
useIdx[i] = 1;
}
/*
* Final pass take the input from the previous and
* deletes the ranges which are flagged to be
* deleted.
*/
for (i = 0; i < objc; i += 2) {
if (useIdx[i]) {
/*
* We don't need to check the return value
* because all indices are preparsed above.
*/
DeleteChars(textPtr, NULL, NULL,
&indices[i], &indices[i+1]);
}
}
ckfree((char *) indices);
}
}
break;
}
case TEXT_DLINEINFO: {
int x, y, width, height, base;
CONST TkTextIndex *indexPtr;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index");
result = TCL_ERROR;
goto done;
}
indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
if (indexPtr == NULL) {
result = TCL_ERROR;
goto done;
}
if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width,
&height, &base) == 0) {
char buf[TCL_INTEGER_SPACE * 5];
sprintf(buf, "%d %d %d %d %d", x, y, width, height, base);
Tcl_SetResult(interp, buf, TCL_VOLATILE);
}
break;
}
case TEXT_DUMP: {
result = TextDumpCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_EDIT: {
result = TextEditCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_GET: {
Tcl_Obj *objPtr = NULL;
int i, found = 0;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
result = TCL_ERROR;
goto done;
}
for (i = 2; i < objc; i += 2) {
CONST TkTextIndex *index1Ptr, *index2Ptr;
TkTextIndex index2;
index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i]);
if (index1Ptr == NULL) {
if (objPtr) {
Tcl_DecrRefCount(objPtr);
}
result = TCL_ERROR;
goto done;
}
if (i+1 == objc) {
TkTextIndexForwChars(index1Ptr, 1, &index2);
index2Ptr = &index2;
} else {
index2Ptr = TkTextGetIndexFromObj(interp, textPtr,
objv[i+1]);
if (index2Ptr == NULL) {
if (objPtr) {
Tcl_DecrRefCount(objPtr);
}
result = TCL_ERROR;
goto done;
}
}
if (TkTextIndexCmp(index1Ptr, index2Ptr) < 0) {
/*
* We want to move the text we get from the window
* into the result, but since this could in principle
* be a megabyte or more, we want to do it
* efficiently!
*/
Tcl_Obj *get = TextGetText(index1Ptr, index2Ptr);
found++;
if (found == 1) {
Tcl_SetObjResult(interp, get);
} else {
if (found == 2) {
/*
* Move the first item we put into the result into
* the first element of the list object.
*/
objPtr = Tcl_NewObj();
Tcl_ListObjAppendElement(NULL, objPtr,
Tcl_GetObjResult(interp));
}
Tcl_ListObjAppendElement(NULL, objPtr, get);
}
}
}
if (found > 1) {
Tcl_SetObjResult(interp, objPtr);
}
break;
}
case TEXT_IMAGE: {
result = TkTextImageCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_INDEX: {
CONST TkTextIndex *indexPtr;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index");
result = TCL_ERROR;
goto done;
}
indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
if (indexPtr == NULL) {
result = TCL_ERROR;
goto done;
}
Tcl_SetObjResult(interp, TkTextNewIndexObj(textPtr, indexPtr));
break;
}
case TEXT_INSERT: {
CONST TkTextIndex *indexPtr;
if (objc < 4) {
Tcl_WrongNumArgs(interp, 2, objv,
"index chars ?tagList chars tagList ...?");
result = TCL_ERROR;
goto done;
}
indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
if (indexPtr == NULL) {
result = TCL_ERROR;
goto done;
}
if (textPtr->state == TK_TEXT_STATE_NORMAL) {
TkTextIndex index1, index2;
int j;
index1 = *indexPtr;
for (j = 3; j < objc; j += 2) {
/*
* Here we rely on this call to modify index1 if
* it is outside the acceptable range. In particular,
* if index1 is "end", it must be set to the last
* allowable index for insertion, otherwise
* subsequent tag insertions will fail.
*/
int length = InsertChars(textPtr, &index1, objv[j]);
if (objc > (j+1)) {
Tcl_Obj **tagNamePtrs;
TkTextTag **oldTagArrayPtr;
int numTags;
TkTextIndexForwBytes(&index1, length, &index2);
oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags);
if (oldTagArrayPtr != NULL) {
int i;
for (i = 0; i < numTags; i++) {
TkBTreeTag(&index1, &index2,
oldTagArrayPtr[i], 0);
}
ckfree((char *) oldTagArrayPtr);
}
if (Tcl_ListObjGetElements(interp, objv[j+1],
&numTags, &tagNamePtrs)
!= TCL_OK) {
result = TCL_ERROR;
goto done;
} else {
int i;
for (i = 0; i < numTags; i++) {
TkBTreeTag(&index1, &index2,
TkTextCreateTag(textPtr,
Tcl_GetString(tagNamePtrs[i])), 1);
}
index1 = index2;
}
}
}
}
break;
}
case TEXT_MARK: {
result = TkTextMarkCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_SCAN: {
result = TkTextScanCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_SEARCH: {
result = TextSearchCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_SEE: {
result = TkTextSeeCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_TAG: {
result = TkTextTagCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_WINDOW: {
result = TkTextWindowCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_XVIEW: {
result = TkTextXviewCmd(textPtr, interp, objc, objv);
break;
}
case TEXT_YVIEW: {
result = TkTextYviewCmd(textPtr, interp, objc, objv);
break;
}
}
done:
Tcl_Release((ClientData) textPtr);
return result;
}