diff options
Diffstat (limited to 'generic')
-rw-r--r-- | generic/tkCanvas.c | 6 | ||||
-rw-r--r-- | generic/tkText.c | 1609 | ||||
-rw-r--r-- | generic/tkText.h | 135 | ||||
-rw-r--r-- | generic/tkTextBTree.c | 325 | ||||
-rw-r--r-- | generic/tkTextDisp.c | 2421 | ||||
-rw-r--r-- | generic/tkTextImage.c | 239 | ||||
-rw-r--r-- | generic/tkTextIndex.c | 679 | ||||
-rw-r--r-- | generic/tkTextMark.c | 37 | ||||
-rw-r--r-- | generic/tkTextTag.c | 124 | ||||
-rw-r--r-- | generic/tkTextWind.c | 38 |
10 files changed, 4597 insertions, 1016 deletions
diff --git a/generic/tkCanvas.c b/generic/tkCanvas.c index 4bc01dd..e1068b0 100644 --- a/generic/tkCanvas.c +++ b/generic/tkCanvas.c @@ -12,7 +12,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkCanvas.c,v 1.23 2003/09/29 23:15:19 dkf Exp $ + * RCS: @(#) $Id: tkCanvas.c,v 1.24 2003/10/31 09:02:08 vincentdarley Exp $ */ /* #define USE_OLD_TAG_SEARCH 1 */ @@ -291,7 +291,7 @@ static int FindArea _ANSI_ARGS_((Tcl_Interp *interp, TkCanvas *canvasPtr, Tcl_Obj *CONST *argv, Tk_Uid uid, int enclosed)); static double GridAlign _ANSI_ARGS_((double coord, double spacing)); -CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc, +static CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc, Tcl_Obj *CONST *objv)); static void InitCanvas _ANSI_ARGS_((void)); #ifdef USE_OLD_TAG_SEARCH @@ -5494,7 +5494,7 @@ CanvasSetOrigin(canvasPtr, xOrigin, yOrigin) *---------------------------------------------------------------------- */ /* ARGSUSED */ -CONST char ** +static CONST char ** TkGetStringsFromObjs(argc, objv) int argc; Tcl_Obj *CONST objv[]; diff --git a/generic/tkText.c b/generic/tkText.c index 4ec38d7..b0b6a21 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -2,10 +2,10 @@ * tkText.c -- * * This module provides a big chunk of the implementation of - * multi-line editable text widgets for Tk. Among other things, - * it provides the Tcl command interfaces to text widgets and - * the display code. The B-tree representation of text is - * implemented elsewhere. + * multi-line editable text widgets for Tk. Among other things, it + * provides the Tcl command interfaces to text widgets. The B-tree + * representation of text and its actual display are implemented + * elsewhere. * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1996 Sun Microsystems, Inc. @@ -14,7 +14,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkText.c,v 1.38 2003/05/27 15:35:52 vincentdarley Exp $ + * RCS: @(#) $Id: tkText.c,v 1.39 2003/10/31 09:02:08 vincentdarley Exp $ */ #include "default.h" @@ -61,12 +61,16 @@ static Tk_OptionSpec optionSpecs[] = { DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border), 0, (ClientData) DEF_TEXT_BG_MONO, 0}, {TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL, - (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", 0}, + (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", + TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL, (char *) NULL, 0, -1, 0, (ClientData) "-background", 0}, + {TK_OPTION_BOOLEAN, "-blockcursor", "blockCursor", + "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1, + Tk_Offset(TkText, insertCursorType), 0, 0, 0}, {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_TEXT_BORDER_WIDTH, -1, Tk_Offset(TkText, borderWidth), - 0, 0, 0}, + 0, 0, TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor), TK_OPTION_NULL_OK, 0, 0}, @@ -76,7 +80,8 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL, (char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0}, {TK_OPTION_FONT, "-font", "font", "Font", - DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0, 0}, + DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0, + TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground", DEF_TEXT_FG, -1, Tk_Offset(TkText, fgColor), 0, 0, 0}, @@ -91,7 +96,7 @@ static Tk_OptionSpec optionSpecs[] = { 0, 0, 0}, {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness", DEF_TEXT_HIGHLIGHT_WIDTH, -1, - Tk_Offset(TkText, highlightWidth), 0, 0, 0}, + Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground", DEF_TEXT_INSERT_BG, -1, Tk_Offset(TkText, insertBorder), @@ -112,7 +117,8 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_INT, "-maxundo", "maxUndo", "MaxUndo", DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndo), 0, 0, 0}, {TK_OPTION_PIXELS, "-padx", "padX", "Pad", - DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0, 0}, + DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0, + TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_PIXELS, "-pady", "padY", "Pad", DEF_TEXT_PADY, -1, Tk_Offset(TkText, padY), 0, 0, 0}, {TK_OPTION_RELIEF, "-relief", "relief", "Relief", @@ -132,29 +138,30 @@ static Tk_OptionSpec optionSpecs[] = { DEF_TEXT_SET_GRID, -1, Tk_Offset(TkText, setGrid), 0, 0, 0}, {TK_OPTION_PIXELS, "-spacing1", "spacing1", "Spacing", DEF_TEXT_SPACING1, -1, Tk_Offset(TkText, spacing1), - TK_OPTION_DONT_SET_DEFAULT, 0 , 0 }, + TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY }, {TK_OPTION_PIXELS, "-spacing2", "spacing2", "Spacing", DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2), - TK_OPTION_DONT_SET_DEFAULT, 0 , 0 }, + TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY }, {TK_OPTION_PIXELS, "-spacing3", "spacing3", "Spacing", DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3), - TK_OPTION_DONT_SET_DEFAULT, 0 , 0 }, + TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY }, {TK_OPTION_STRING_TABLE, "-state", "state", "State", DEF_TEXT_STATE, -1, Tk_Offset(TkText, state), 0, (ClientData) stateStrings, 0}, {TK_OPTION_STRING, "-tabs", "tabs", "Tabs", DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionPtr), -1, - TK_OPTION_NULL_OK, 0, 0}, + TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_TEXT_TAKE_FOCUS, -1, Tk_Offset(TkText, takeFocus), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_BOOLEAN, "-undo", "undo", "Undo", DEF_TEXT_UNDO, -1, Tk_Offset(TkText, undo), 0, 0 , 0}, {TK_OPTION_INT, "-width", "width", "Width", - DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0, 0}, + DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0, + TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_STRING_TABLE, "-wrap", "wrap", "Wrap", DEF_TEXT_WRAP, -1, Tk_Offset(TkText, wrapMode), - 0, (ClientData) wrapStrings, 0}, + 0, (ClientData) wrapStrings, TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", DEF_TEXT_XSCROLL_COMMAND, -1, Tk_Offset(TkText, xScrollCmd), TK_OPTION_NULL_OK, 0, 0}, @@ -195,6 +202,14 @@ typedef struct SearchSpec { int noCase; /* Case-insenstivive? */ int noLineStop; /* If not set, a regexp search will * use the TCL_REG_NLSTOP flag */ + int overlap; /* If set, results from multiple + * searches (-all) are allowed to + * overlap each other. */ + int strictLimits; /* If set, matches must be + * completely inside the from,to + * range. Otherwise the limits + * only apply to the start of each + * match. */ int all; /* Whether all or the first match should * be reported */ int startLine; /* First line to examine */ @@ -248,12 +263,16 @@ int tkTextDebug = 0; static int ConfigureText _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, int objc, Tcl_Obj *CONST objv[])); static int DeleteChars _ANSI_ARGS_((TkText *textPtr, - Tcl_Obj *index1Obj, Tcl_Obj *index2Obj, CONST TkTextIndex *indexPtr1, - CONST TkTextIndex *indexPtr2)); -static void DestroyText _ANSI_ARGS_((char *memPtr)); + CONST TkTextIndex *indexPtr2, int noViewUpdate)); +static int CountIndices _ANSI_ARGS_((CONST TkText *textPtr, + CONST TkTextIndex *indexPtr1, + CONST TkTextIndex *indexPtr2, + TkTextCountType type)); +static void DestroyText _ANSI_ARGS_((TkText *textPtr)); static int InsertChars _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr, Tcl_Obj *stringPtr)); + TkTextIndex *indexPtr, Tcl_Obj *stringPtr, + int noViewUpdate)); static void TextBlinkProc _ANSI_ARGS_((ClientData clientData)); static void TextCmdDeletedProc _ANSI_ARGS_(( ClientData clientData)); @@ -263,6 +282,16 @@ static int TextFetchSelection _ANSI_ARGS_((ClientData clientData, int offset, char *buffer, int maxBytes)); static int TextIndexSortProc _ANSI_ARGS_((CONST VOID *first, CONST VOID *second)); +static int TextInsertCmd _ANSI_ARGS_((TkText *textPtr, + Tcl_Interp *interp, + int objc, Tcl_Obj *CONST objv[], + CONST TkTextIndex *indexPtr, int noViewUpdate)); +static int TextReplaceCmd _ANSI_ARGS_((TkText *textPtr, + Tcl_Interp *interp, + CONST TkTextIndex *indexFromPtr, + CONST TkTextIndex *indexToPtr, + int objc, Tcl_Obj *CONST objv[], + int noViewUpdate)); static int TextSearchCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); @@ -272,8 +301,10 @@ static int TextEditCmd _ANSI_ARGS_((TkText *textPtr, static int TextWidgetObjCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); -static void TextWorldChanged _ANSI_ARGS_(( +static void TextWorldChangedCallback _ANSI_ARGS_(( ClientData instanceData)); +static void TextWorldChanged _ANSI_ARGS_((TkText *textPtr, + int mask)); static int TextDumpCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); @@ -281,13 +312,15 @@ static void DumpLine _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, int what, TkTextLine *linePtr, int start, int end, int lineno, CONST char *command)); -static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp, char *key, - char *value, CONST char * command, +static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp, + CONST char *key, + CONST char *value, CONST char * command, CONST TkTextIndex *index, int what)); static int TextEditUndo _ANSI_ARGS_((TkText *textPtr)); static int TextEditRedo _ANSI_ARGS_((TkText *textPtr)); -static Tcl_Obj* TextGetText _ANSI_ARGS_((CONST TkTextIndex * index1, - CONST TkTextIndex * index2)); +static Tcl_Obj* TextGetText _ANSI_ARGS_((CONST TkText *textPtr, + CONST TkTextIndex * index1, + CONST TkTextIndex * index2, int visibleOnly)); static void UpdateDirtyFlag _ANSI_ARGS_((TkText *textPtr)); static void TextPushUndoAction _ANSI_ARGS_((TkText *textPtr, Tcl_Obj *undoString, int insert, @@ -314,7 +347,7 @@ static SearchLineIndexProc TextSearchGetLineIndex; static Tk_ClassProcs textClass = { sizeof(Tk_ClassProcs), /* size */ - TextWorldChanged, /* worldChangedProc */ + TextWorldChangedCallback, /* worldChangedProc */ }; @@ -387,9 +420,16 @@ Tk_TextObjCmd(clientData, interp, objc, objv) textPtr->relief = TK_RELIEF_FLAT; textPtr->cursor = None; textPtr->charWidth = 1; + textPtr->charHeight = 10; textPtr->wrapMode = TEXT_WRAPMODE_CHAR; textPtr->prevWidth = Tk_Width(new); textPtr->prevHeight = Tk_Height(new); + /* + * This refCount will be held until DestroyText is called. + * Note also that the following call to 'TkTextCreateDInfo' + * will add more refCounts. + */ + textPtr->refCount = 1; TkTextCreateDInfo(textPtr); TkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex); TkTextSetYView(textPtr, &startIndex, 0); @@ -402,7 +442,6 @@ Tk_TextObjCmd(clientData, interp, objc, objv) textPtr->lastEditMode = TK_TEXT_EDIT_OTHER; textPtr->tabOptionPtr = NULL; textPtr->stateEpoch = 0; - textPtr->refCount = 1; /* * Create the "sel" tag and the "current" and "insert" marks. @@ -486,16 +525,17 @@ TextWidgetObjCmd(clientData, interp, objc, objv) 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 + "bbox", "cget", "compare", "configure", "count", "debug", + "delete", "dlineinfo", "dump", "edit", "get", "image", "index", + "insert", "mark", "replace", "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 + TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_COUNT, + TEXT_DEBUG, TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT, + TEXT_GET, TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK, + TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE, + TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW }; if (objc < 2) { @@ -507,7 +547,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) &index) != TCL_OK) { return TCL_ERROR; } - Tcl_Preserve((ClientData) textPtr); + textPtr->refCount++; switch ((enum options) index) { case TEXT_BBOX: { @@ -525,11 +565,19 @@ TextWidgetObjCmd(clientData, interp, objc, objv) goto done; } if (TkTextCharBbox(textPtr, indexPtr, &x, &y, - &width, &height) == 0) { - char buf[TCL_INTEGER_SPACE * 4]; + &width, &height, NULL) == 0) { + Tcl_Obj *listObj = Tcl_NewListObj(0, NULL); + + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(x)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(y)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(width)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(height)); - sprintf(buf, "%d %d %d %d", x, y, width, height); - Tcl_SetResult(interp, buf, TCL_VOLATILE); + Tcl_SetObjResult(interp, listObj); } break; } @@ -616,6 +664,176 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } break; } + case TEXT_COUNT: { + CONST TkTextIndex *indexFromPtr, *indexToPtr; + int i, found = 0, update = 0; + Tcl_Obj *objPtr = NULL; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, "?options? index1 index2"); + result = TCL_ERROR; + goto done; + } + + indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[objc-2]); + if (indexFromPtr == NULL) { + result = TCL_ERROR; + goto done; + } + indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[objc-1]); + if (indexToPtr == NULL) { + result = TCL_ERROR; + goto done; + } + + for (i = 2; i < objc-2; i++) { + int value; + int length; + CONST char *option = Tcl_GetStringFromObj(objv[i],&length); + char c; + if (length < 2 || option[0] != '-') { + badOption: + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "bad option \"", + Tcl_GetString(objv[i]), + "\" must be -chars, -displaychars, -displayindices, ", + "-displaylines, -indices, -lines, -update, ", + "-xpixels, or -ypixels", NULL); + result = TCL_ERROR; + goto done; + } + c = option[1]; + if (c == 'c' && !strncmp("-chars",option,length)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_CHARS); + } else if (c == 'd' && !strncmp("-displaychars", option, + length) && (length > 8)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_DISPLAY_CHARS); + } else if (c == 'd' && !strncmp("-displayindices", option, + length) && (length > 8)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_DISPLAY_INDICES); + } else if (c == 'd' && !strncmp("-displaylines", option, + length) && (length > 8)) { + TkTextLine *fromPtr, *lastPtr; + TkTextIndex index; + + int compare = TkTextIndexCmp(indexFromPtr, indexToPtr); + value = 0; + + if (compare == 0) goto countDone; + + if (compare > 0) { + CONST TkTextIndex *tmpPtr = indexFromPtr; + indexFromPtr = indexToPtr; + indexToPtr = tmpPtr; + } + + lastPtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + fromPtr = indexFromPtr->linePtr; + if (fromPtr == lastPtr) { + goto countDone; + } + + /* + * Caution: we must NEVER call TkTextUpdateOneLine + * with the last artificial line in the widget. + */ + while (fromPtr != indexToPtr->linePtr) { + value += TkTextUpdateOneLine(textPtr, fromPtr); + fromPtr = TkBTreeNextLine(fromPtr); + } + /* + * Now we need to adjust the count to add on the + * number of display lines in the last logical line, + * and subtract off the number of display lines + * overcounted in the first logical line. This logic + * is still ok if both indices are in the same + * logical line. + */ + index.linePtr = indexFromPtr->linePtr; + index.byteIndex = 0; + while (1) { + TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL); + if (index.byteIndex >= indexFromPtr->byteIndex) { + break; + } + TkTextIndexForwBytes(&index, 1, &index); + value--; + } + if (indexToPtr->linePtr != lastPtr) { + index.linePtr = indexToPtr->linePtr; + index.byteIndex = 0; + while (1) { + TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL); + if (index.byteIndex >= indexToPtr->byteIndex) { + break; + } + TkTextIndexForwBytes(&index, 1, &index); + value++; + } + } + + if (compare > 0) { + value = -value; + } + } else if (c == 'i' && !strncmp("-indices",option,length)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_INDICES); + } else if (c == 'l' && !strncmp("-lines",option,length)) { + value = TkBTreeLineIndex(indexToPtr->linePtr) + - TkBTreeLineIndex(indexFromPtr->linePtr); + } else if (c == 'u' && !strncmp("-update",option,length)) { + update = 1; + continue; + } else if (c == 'x' && !strncmp("-xpixels",option,length)) { + int x1, x2; + TkTextIndex index; + index = *indexFromPtr; + TkTextFindDisplayLineEnd(textPtr, &index, 0, &x1); + index = *indexToPtr; + TkTextFindDisplayLineEnd(textPtr, &index, 0, &x2); + value = x2 - x1; + } else if (c == 'y' && !strncmp("-ypixels",option,length)) { + if (update) { + TkTextUpdateLineMetrics(textPtr, + TkBTreeLineIndex(indexFromPtr->linePtr), + TkBTreeLineIndex(indexToPtr->linePtr), -1); + } + value = TkTextIndexYPixels(textPtr, indexToPtr) + - TkTextIndexYPixels(textPtr, indexFromPtr); + } else { + goto badOption; + } + countDone: + found++; + if (found == 1) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(value)); + } 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, Tcl_NewIntObj(value)); + } + } + if (found == 0) { + /* Use the default '-indices' */ + int value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_INDICES); + Tcl_SetObjResult(interp, Tcl_NewIntObj(value)); + } else if (found > 1) { + Tcl_SetObjResult(interp, objPtr); + } + break; + } case TEXT_DEBUG: { if (objc > 3) { Tcl_WrongNumArgs(interp, 2, objv, "boolean"); @@ -645,8 +863,29 @@ TextWidgetObjCmd(clientData, interp, objc, objv) /* * Simple case requires no predetermination of indices. */ - result = DeleteChars(textPtr, objv[2], - (objc == 4) ? objv[3] : NULL, NULL, NULL); + CONST TkTextIndex *indexPtr1, *indexPtr2; + + /* + * Parse the starting and stopping indices. + */ + + indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, + textPtr, objv[2]); + if (indexPtr1 == NULL) { + result = TCL_ERROR; + goto done; + } + if (objc == 4) { + indexPtr2 = TkTextGetIndexFromObj(textPtr->interp, + textPtr, objv[3]); + if (indexPtr2 == NULL) { + result = TCL_ERROR; + goto done; + } + } else { + indexPtr2 = NULL; + } + DeleteChars(textPtr, indexPtr1, indexPtr2, 0); } else { int i; /* @@ -683,7 +922,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) */ if (objc & 1) { indices[i] = indices[i-1]; - TkTextIndexForwChars(&indices[i], 1, &indices[i]); + TkTextIndexForwChars(&indices[i], 1, &indices[i], + COUNT_INDICES); objc++; } useIdx = (char *) ckalloc((unsigned) objc); @@ -740,8 +980,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) * We don't need to check the return value * because all indices are preparsed above. */ - DeleteChars(textPtr, NULL, NULL, - &indices[i], &indices[i+1]); + DeleteChars(textPtr, &indices[i], + &indices[i+1], 0); } } ckfree((char *) indices); @@ -765,10 +1005,20 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width, &height, &base) == 0) { - char buf[TCL_INTEGER_SPACE * 5]; + Tcl_Obj *listObj = Tcl_NewListObj(0, NULL); - sprintf(buf, "%d %d %d %d %d", x, y, width, height, base); - Tcl_SetResult(interp, buf, TCL_VOLATILE); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(x)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(y)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(width)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(height)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(base)); + + Tcl_SetObjResult(interp, listObj); } break; } @@ -782,14 +1032,37 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } case TEXT_GET: { Tcl_Obj *objPtr = NULL; - int i, found = 0; - + int i, found = 0, visible = 0; + CONST char *name; + int length; + if (objc < 3) { - Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?"); + Tcl_WrongNumArgs(interp, 2, objv, + "?-displaychars? ?--? index1 ?index2 ...?"); result = TCL_ERROR; goto done; } - for (i = 2; i < objc; i += 2) { + + /* + * Simple, restrictive argument parsing. The only options are -- + * and -displaychars (or any unique prefix). + */ + i = 2; + if (objc > 3) { + name = Tcl_GetStringFromObj(objv[i], &length); + if (length > 1 && name[0] == '-') { + if (strncmp("-displaychars", name, length) == 0) { + i++; + visible = 1; + name = Tcl_GetStringFromObj(objv[i], &length); + } + if ((i < objc-1) && (length == 2) + && (strcmp("--", name) == 0)) { + i++; + } + } + } + for (; i < objc; i += 2) { CONST TkTextIndex *index1Ptr, *index2Ptr; TkTextIndex index2; @@ -802,7 +1075,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) goto done; } if (i+1 == objc) { - TkTextIndexForwChars(index1Ptr, 1, &index2); + TkTextIndexForwChars(index1Ptr, 1, &index2, COUNT_INDICES); index2Ptr = &index2; } else { index2Ptr = TkTextGetIndexFromObj(interp, textPtr, @@ -822,7 +1095,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) * be a megabyte or more, we want to do it * efficiently! */ - Tcl_Obj *get = TextGetText(index1Ptr, index2Ptr); + Tcl_Obj *get = TextGetText(textPtr, index1Ptr, + index2Ptr, visible); found++; if (found == 1) { Tcl_SetObjResult(interp, get); @@ -881,51 +1155,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) 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; - } - } - } + result = TextInsertCmd(textPtr, interp, objc-3, objv+3, + indexPtr, 0); } break; } @@ -933,6 +1164,113 @@ TextWidgetObjCmd(clientData, interp, objc, objv) result = TkTextMarkCmd(textPtr, interp, objc, objv); break; } + case TEXT_REPLACE: { + CONST TkTextIndex *indexFromPtr, *indexToPtr; + + if (objc < 5) { + Tcl_WrongNumArgs(interp, 2, objv, + "index1 index2 chars ?tagList chars tagList ...?"); + result = TCL_ERROR; + goto done; + } + indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]); + if (indexFromPtr == NULL) { + result = TCL_ERROR; + goto done; + } + indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[3]); + if (indexToPtr == NULL) { + result = TCL_ERROR; + goto done; + } + if (TkTextIndexCmp(indexFromPtr, indexToPtr) > 0) { + Tcl_AppendResult(interp, "Index \"", Tcl_GetString(objv[3]), + "\" before \"", Tcl_GetString(objv[2]), + "\" in the text.", NULL); + result = TCL_ERROR; + goto done; + } + if (textPtr->state == TK_TEXT_STATE_NORMAL) { + int lineNum, byteIndex; + TkTextIndex index; + /* + * The 'replace' operation is quite complex to do + * correctly, because we want a number of criteria + * to hold: + * + * 1. The insertion point shouldn't move, unless + * it is within the deleted range. In this case + * it should end up after the new text. + * + * 2. The window should not change the text it + * shows -- should not scroll vertically -- unless + * the result of the replace is that the insertion + * position which used to be on-screen is now + * off-screen. + */ + byteIndex = textPtr->topIndex.byteIndex; + lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr); + + TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, + &index); + if ((TkTextIndexCmp(indexFromPtr, &index) < 0) + && (TkTextIndexCmp(indexToPtr, &index) > 0)) { + /* + * The insertion point is inside the range to be + * replaced, so we have to do some calculations to + * ensure it doesn't move unnecessarily. + */ + int deleteInsertOffset, insertLength, j; + + insertLength = 0; + for (j = 4; j < objc; j += 2) { + insertLength += Tcl_GetCharLength(objv[j]); + } + + /* + * Calculate 'deleteInsertOffset' as an offset we + * will apply to the insertion point after this + * operation. + */ + deleteInsertOffset = CountIndices(textPtr, indexFromPtr, + &index, COUNT_CHARS); + if (deleteInsertOffset > insertLength) { + deleteInsertOffset = insertLength; + } + + result = TextReplaceCmd(textPtr, interp, + indexFromPtr, indexToPtr, + objc, objv, 1); + + if (result == TCL_OK) { + /* + * Move the insertion position to the correct + * place + */ + TkTextIndexForwChars(indexFromPtr, deleteInsertOffset, + &index, COUNT_INDICES); + TkBTreeUnlinkSegment(textPtr->tree, + textPtr->insertMarkPtr, + textPtr->insertMarkPtr->body.mark.linePtr); + TkBTreeLinkSegment(textPtr->insertMarkPtr, &index); + } + } else { + result = TextReplaceCmd(textPtr, interp, + indexFromPtr, indexToPtr, + objc, objv, 0); + } + if (result == TCL_OK) { + /* + * Now ensure the top-line is in the right + * place + */ + TkTextMakeByteIndex(textPtr->tree, lineNum, + byteIndex, &index); + TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST); + } + } + break; + } case TEXT_SCAN: { result = TkTextScanCmd(textPtr, interp, objc, objv); break; @@ -964,7 +1302,68 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } done: - Tcl_Release((ClientData) textPtr); + textPtr->refCount--; + if (textPtr->refCount == 0) { + ckfree((char *) textPtr); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TextReplaceCmd -- + * + * This procedure is invoked to process part of the "replace" widget + * command for text widgets. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +static int +TextReplaceCmd(textPtr, interp, indexFromPtr, indexToPtr, + objc, objv, noViewUpdate) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + CONST TkTextIndex *indexFromPtr;/* Index from which to replace */ + CONST TkTextIndex *indexToPtr; /* Index to which to replace */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ + int noViewUpdate; /* Don't update the view if set */ +{ + int result; + /* + * Perform the deletion and insertion, but ensure + * no undo-separator is placed between the two + * operations. Since we are using the helper procedures + * 'DeleteChars' and 'TextInsertCmd' we have to pretend + * that the autoSeparators setting is off, so that we don't + * get an undo-separator between the delete and insert. + */ + int origAutoSep = textPtr->autoSeparators; + + if (textPtr->undo) { + textPtr->autoSeparators = 0; + if (origAutoSep && textPtr->lastEditMode != TK_TEXT_EDIT_REPLACE) { + TkUndoInsertUndoSeparator(textPtr->undoStack); + } + } + + DeleteChars(textPtr, indexFromPtr, indexToPtr, noViewUpdate); + result = TextInsertCmd(textPtr, interp, objc-4, objv+4, + indexFromPtr, noViewUpdate); + + if (textPtr->undo) { + textPtr->lastEditMode = TK_TEXT_EDIT_REPLACE; + textPtr->autoSeparators = origAutoSep; + } + return result; } @@ -1016,24 +1415,28 @@ TextIndexSortProc(first, second) * * DestroyText -- * - * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release - * to clean up the internal structure of a text at a safe time - * (when no-one is using it anymore). + * This procedure is invoked when we receive a destroy event + * to clean up the internal structure of a text widget. We will + * free up most of the internal structure and delete the + * associated Tcl command. If there are no outstanding + * references to the widget, we also free up the textPtr itself. + * + * The widget has already been flagged as deleted. * * Results: * None. * * Side effects: - * Everything associated with the text is freed up. + * Either everything or almost everything associated with the + * text is freed up. * *---------------------------------------------------------------------- */ static void -DestroyText(memPtr) - char *memPtr; /* Info about text widget. */ +DestroyText(textPtr) + TkText *textPtr; /* Info about text widget. */ { - register TkText *textPtr = (TkText *) memPtr; Tcl_HashSearch search; Tcl_HashEntry *hPtr; TkTextTag *tagPtr; @@ -1048,6 +1451,8 @@ DestroyText(memPtr) */ TkTextFreeDInfo(textPtr); + textPtr->dInfoPtr = NULL; + TkBTreeDestroy(textPtr->tree); for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { @@ -1111,9 +1516,10 @@ ConfigureText(interp, textPtr, objc, objv) { Tk_SavedOptions savedOptions; int oldExport = textPtr->exportSelection; + int mask = 0; if (Tk_SetOptions(interp, (char*)textPtr, textPtr->optionTable, - objc, objv, textPtr->tkwin, &savedOptions, NULL) != TCL_OK) { + objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) { return TCL_ERROR; } @@ -1173,27 +1579,31 @@ ConfigureText(interp, textPtr, objc, objv) } textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr; textPtr->selTagPtr->affectsDisplay = 0; - if ((textPtr->selTagPtr->border != NULL) - || (textPtr->selTagPtr->borderWidth != 0) - || (textPtr->selTagPtr->reliefString != NULL) - || (textPtr->selTagPtr->bgStipple != None) - || (textPtr->selTagPtr->fgColor != NULL) + textPtr->selTagPtr->affectsDisplay = 0; + textPtr->selTagPtr->affectsDisplayGeometry = 0; + if ((textPtr->selTagPtr->elideString != NULL) || (textPtr->selTagPtr->tkfont != None) - || (textPtr->selTagPtr->fgStipple != None) || (textPtr->selTagPtr->justifyString != NULL) || (textPtr->selTagPtr->lMargin1String != NULL) || (textPtr->selTagPtr->lMargin2String != NULL) || (textPtr->selTagPtr->offsetString != NULL) - || (textPtr->selTagPtr->overstrikeString != NULL) || (textPtr->selTagPtr->rMarginString != NULL) || (textPtr->selTagPtr->spacing1String != NULL) || (textPtr->selTagPtr->spacing2String != NULL) || (textPtr->selTagPtr->spacing3String != NULL) || (textPtr->selTagPtr->tabStringPtr != NULL) - || (textPtr->selTagPtr->underlineString != NULL) - || (textPtr->selTagPtr->elideString != NULL) || (textPtr->selTagPtr->wrapMode != TEXT_WRAPMODE_NULL)) { textPtr->selTagPtr->affectsDisplay = 1; + textPtr->selTagPtr->affectsDisplayGeometry = 1; + } + if ((textPtr->selTagPtr->border != NULL) + || (textPtr->selTagPtr->reliefString != NULL) + || (textPtr->selTagPtr->bgStipple != None) + || (textPtr->selTagPtr->fgColor != NULL) + || (textPtr->selTagPtr->fgStipple != None) + || (textPtr->selTagPtr->overstrikeString != NULL) + || (textPtr->selTagPtr->underlineString != NULL)) { + textPtr->selTagPtr->affectsDisplay = 1; } TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL, textPtr->selTagPtr, 1); @@ -1241,14 +1651,14 @@ ConfigureText(interp, textPtr, objc, objv) textPtr->height = 1; } Tk_FreeSavedOptions(&savedOptions); - TextWorldChanged((ClientData) textPtr); + TextWorldChanged(textPtr, mask); return TCL_OK; } /* *--------------------------------------------------------------------------- * - * TextWorldChanged -- + * TextWorldChangedCallback -- * * This procedure is called when the world has changed in some * way and the widget needs to recompute all its graphics contexts @@ -1266,19 +1676,53 @@ ConfigureText(interp, textPtr, objc, objv) */ static void -TextWorldChanged(instanceData) +TextWorldChangedCallback(instanceData) ClientData instanceData; /* Information about widget. */ { TkText *textPtr; - Tk_FontMetrics fm; textPtr = (TkText *) instanceData; + TextWorldChanged(textPtr, TK_TEXT_LINE_GEOMETRY); +} + +/* + *--------------------------------------------------------------------------- + * + * TextWorldChanged -- + * + * This procedure is called when the world has changed in some + * way and the widget needs to recompute all its graphics contexts + * and determine its new geometry. + * + * Results: + * None. + * + * Side effects: + * Configures all tags in the Text with a empty objc/objv, for + * the side effect of causing all the items to recompute their + * geometry and to be redisplayed. + * + *--------------------------------------------------------------------------- + */ + +static void +TextWorldChanged(textPtr, mask) + TkText *textPtr; /* Information about widget. */ + int mask; /* OR'd collection of bits showing what + * has changed */ +{ + Tk_FontMetrics fm; textPtr->charWidth = Tk_TextWidth(textPtr->tkfont, "0", 1); if (textPtr->charWidth <= 0) { textPtr->charWidth = 1; } Tk_GetFontMetrics(textPtr->tkfont, &fm); + + textPtr->charHeight = fm.linespace; + if (textPtr->charHeight <= 0) { + textPtr->charHeight = 1; + } Tk_GeometryRequest(textPtr->tkwin, textPtr->width * textPtr->charWidth + 2*textPtr->borderWidth + 2*textPtr->padX + 2*textPtr->highlightWidth, @@ -1289,12 +1733,12 @@ TextWorldChanged(instanceData) textPtr->borderWidth + textPtr->highlightWidth); if (textPtr->setGrid) { Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height, - textPtr->charWidth, fm.linespace); + textPtr->charWidth, textPtr->charHeight); } else { Tk_UnsetGrid(textPtr->tkwin); } - TkTextRelayoutWindow(textPtr); + TkTextRelayoutWindow(textPtr, mask); } /* @@ -1331,7 +1775,11 @@ TextEventProc(clientData, eventPtr) } else if (eventPtr->type == ConfigureNotify) { if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin)) || (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) { - TkTextRelayoutWindow(textPtr); + int mask = 0; + if (textPtr->prevWidth != Tk_Width(textPtr->tkwin)) { + mask = 1; + } + TkTextRelayoutWindow(textPtr, mask); textPtr->prevWidth = Tk_Width(textPtr->tkwin); textPtr->prevHeight = Tk_Height(textPtr->tkwin); } @@ -1360,13 +1808,13 @@ TextEventProc(clientData, eventPtr) textPtr->flags |= DESTROYED; /* - * We don't delete the associated Tcl command yet, because - * that will cause textPtr->tkWin to be nulled out, and that - * is needed inside DestroyText to clean up certain tags - * which might have been created (e.g. in the text widget - * styles demo). + * Call 'DestroyTest' to handle the deletion for us. The + * actual textPtr may still exist after this, if there are + * some outstanding references. But we have flagged it + * as DESTROYED just above, so nothing will try to make use + * of it very extensively. */ - Tcl_EventuallyFree((ClientData) textPtr, DestroyText); + DestroyText(textPtr); } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { if (eventPtr->xfocus.detail != NotifyInferior) { Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler); @@ -1385,11 +1833,15 @@ TextEventProc(clientData, eventPtr) TkTextRedrawTag(textPtr, NULL, NULL, textPtr->selTagPtr, 1); #endif TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - TkTextIndexForwChars(&index, 1, &index2); + TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES); + /* + * While we wish to redisplay, no heights have changed, so + * no need to call TkTextInvalidateLineMetrics + */ TkTextChanged(textPtr, &index, &index2); if (textPtr->highlightWidth > 0) { TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth, - textPtr->highlightWidth); + textPtr->highlightWidth); } } } @@ -1451,18 +1903,22 @@ TextCmdDeletedProc(clientData) * Side effects: * The characters in "stringPtr" get added to the text just before * the character indicated by "indexPtr". + * + * Unless 'noViewUpdate' is set, we may adjust the window + * contents' y-position, and scrollbar setting. * *---------------------------------------------------------------------- */ static int -InsertChars(textPtr, indexPtr, stringPtr) +InsertChars(textPtr, indexPtr, stringPtr, noViewUpdate) TkText *textPtr; /* Overall information about text widget. */ TkTextIndex *indexPtr; /* Where to insert new characters. May be * modified if the index is not valid * for insertion (e.g. if at "end"). */ Tcl_Obj *stringPtr; /* Null-terminated string containing new * information to add to text. */ + int noViewUpdate; /* Don't update the view if set */ { int lineIndex, resetView, offset, length; @@ -1519,7 +1975,7 @@ InsertChars(textPtr, indexPtr, stringPtr) UpdateDirtyFlag(textPtr); - if (resetView) { + if (resetView && !noViewUpdate) { TkTextIndex newTop; TkTextMakeByteIndex(textPtr->tree, lineIndex, 0, &newTop); TkTextIndexForwBytes(&newTop, offset, &newTop); @@ -1643,69 +2099,94 @@ TextPushUndoAction (textPtr, undoString, insert, index1Ptr, index2Ptr) /* *---------------------------------------------------------------------- * + * CountIndices -- + * + * This procedure implements most of the functionality of the + * "count" widget command. + * + * Note that 'textPtr' is only used if we need to check for elided + * attributes, i.e. if type is COUNT_DISPLAY_INDICES or + * COUNT_DISPLAY_CHARS. + * + * Results: + * Returns the number of characters in the range. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +CountIndices(textPtr, indexPtr1, indexPtr2, type) + CONST TkText *textPtr; /* Overall information about text widget. */ + CONST TkTextIndex *indexPtr1;/* Index describing location of first + * character to delete. */ + CONST TkTextIndex *indexPtr2;/* Index describing location of last + * character to delete. NULL means just + * delete the one character given by + * indexPtr1. */ + TkTextCountType type; /* The kind of indices to count */ +{ + /* + * Order the starting and stopping indices. + */ + + int compare = TkTextIndexCmp(indexPtr1, indexPtr2); + + if (compare == 0) { + return 0; + } else if (compare > 0) { + return -TkTextIndexCount(textPtr, indexPtr2, indexPtr1, type); + } else { + return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type); + } +} + +/* + *---------------------------------------------------------------------- + * * DeleteChars -- * * This procedure implements most of the functionality of the * "delete" widget command. * * Results: - * Returns a standard Tcl result, and leaves an error message - * in textPtr->interp if there is an error. + * Returns a standard Tcl result, currently always TCL_OK. * * Side effects: * Characters get deleted from the text. + * + * Unless 'noViewUpdate' is set, we may adjust the window + * contents' y-position, and scrollbar setting. * *---------------------------------------------------------------------- */ static int -DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2) +DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate) TkText *textPtr; /* Overall information about text widget. */ - Tcl_Obj *index1Obj; /* Object describing location of first - * character to delete. */ - Tcl_Obj *index2Obj; /* Object describing location of last - * character to delete. NULL means just - * delete the one character given by - * index1Obj. */ CONST TkTextIndex *indexPtr1;/* Index describing location of first * character to delete. */ CONST TkTextIndex *indexPtr2;/* Index describing location of last * character to delete. NULL means just * delete the one character given by * indexPtr1. */ + int noViewUpdate; /* Don't update the view if set */ { int line1, line2, line, byteIndex, resetView; TkTextIndex index1, index2; /* - * Parse the starting and stopping indices. + * Prepare the starting and stopping indices. */ - if (index1Obj != NULL) { - indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, textPtr, index1Obj); - if (indexPtr1 == NULL) { - return TCL_ERROR; - } - index1 = *indexPtr1; - if (index2Obj != NULL) { - indexPtr2 = TkTextGetIndexFromObj(textPtr->interp, textPtr, - index2Obj); - if (indexPtr2 == NULL) { - return TCL_ERROR; - } - index2 = *indexPtr2; - } else { - index2 = index1; - TkTextIndexForwChars(&index2, 1, &index2); - } + index1 = *indexPtr1; + if (indexPtr2 != NULL) { + index2 = *indexPtr2; } else { - index1 = *indexPtr1; - if (indexPtr2 != NULL) { - index2 = *indexPtr2; - } else { - index2 = index1; - TkTextIndexForwChars(&index2, 1, &index2); - } + index2 = index1; + TkTextIndexForwChars(&index2, 1, &index2, COUNT_INDICES); } /* @@ -1736,10 +2217,10 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2) TkTextIndex oldIndex2; oldIndex2 = index2; - TkTextIndexBackChars(&oldIndex2, 1, &index2); + TkTextIndexBackChars(&oldIndex2, 1, &index2, COUNT_INDICES); line2--; if ((index1.byteIndex == 0) && (line1 != 0)) { - TkTextIndexBackChars(&index1, 1, &index1); + TkTextIndexBackChars(&index1, 1, &index1, COUNT_INDICES); line1--; } arrayPtr = TkBTreeGetTags(&index2, &arraySize); @@ -1751,6 +2232,54 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2) } } + if (line1 < line2) { + /* + * We are deleting more than one line. For speed, + * we remove all tags from the range first. If we + * don't do this, the code below can (when there are + * many tags) grow non-linearly in execution time. + */ + Tcl_HashSearch search; + Tcl_HashEntry *hPtr; + int i; + + for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); + hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) { + TkTextTag *tagPtr; + tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); + if (TkBTreeTag(&index1, &index2, tagPtr, 0)) { + /* + * If the tag is "sel", and we actually adjusted anything + * then grab the selection if we're supposed to export it + * and don't already have it. Also, invalidate + * partially-completed selection retrievals. + * + * This code copied from tkTextTag.c's 'tag remove' + */ + + if (tagPtr == textPtr->selTagPtr) { + XEvent event; + /* + * Send an event that the selection changed. + * This is equivalent to + * "event generate $textWidget <<Selection>>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); + Tk_HandleEvent(&event); + + textPtr->abortSelections = 1; + } + } + } + } + /* * Tell the display what's about to happen so it can discard * obsolete display information, then do the deletion. Also, @@ -1815,14 +2344,15 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2) textPtr->lastEditMode = TK_TEXT_EDIT_DELETE; - get = TextGetText(&index1, &index2); + get = TextGetText(textPtr, &index1, &index2, 0); TextPushUndoAction(textPtr, get, 0, &index1, &index2); } UpdateDirtyFlag(textPtr); textPtr->stateEpoch ++; TkBTreeDeleteChars(&index1, &index2); - if (resetView) { + + if (resetView && !noViewUpdate) { TkTextMakeByteIndex(textPtr->tree, line, byteIndex, &index1); TkTextSetYView(textPtr, &index1, 0); } @@ -2061,7 +2591,7 @@ TextBlinkProc(clientData) { register TkText *textPtr = (TkText *) clientData; TkTextIndex index; - int x, y, w, h; + int x, y, w, h, charWidth; if ((textPtr->state == TK_TEXT_STATE_DISABLED) || !(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) { @@ -2077,15 +2607,98 @@ TextBlinkProc(clientData) textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr); } TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - if (TkTextCharBbox(textPtr, &index, &x, &y, &w, &h) == 0) { - TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y, - textPtr->insertWidth, h); + if (TkTextCharBbox(textPtr, &index, &x, &y, &w, &h, &charWidth) == 0) { + if (textPtr->insertCursorType) { + /* Block cursor */ + TkTextRedrawRegion(textPtr, x - textPtr->width / 2, y, + charWidth + textPtr->insertWidth / 2, h); + } else { + /* I-beam cursor */ + TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y, + textPtr->insertWidth, h); + } } } /* *---------------------------------------------------------------------- * + * TextInsertCmd -- + * + * This procedure is invoked to process the "insert" and "replace" + * widget commands for text widgets. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + * Unless 'noViewUpdate' is set, we may adjust the window + * contents' y-position, and scrollbar setting. + * + *---------------------------------------------------------------------- + */ + +static int +TextInsertCmd(textPtr, interp, objc, objv, indexPtr, noViewUpdate) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ + CONST TkTextIndex *indexPtr;/* Index at which to insert */ + int noViewUpdate; /* Don't update the view if set */ +{ + TkTextIndex index1, index2; + int j; + + index1 = *indexPtr; + for (j = 0; 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], noViewUpdate); + 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) { + return TCL_ERROR; + } else { + int i; + + for (i = 0; i < numTags; i++) { + TkBTreeTag(&index1, &index2, + TkTextCreateTag(textPtr, + Tcl_GetString(tagNamePtrs[i])), 1); + } + index1 = index2; + } + } + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * TextSearchCmd -- * * This procedure is invoked to process the "search" widget command @@ -2113,12 +2726,14 @@ TextSearchCmd(textPtr, interp, objc, objv) static CONST char *switchStrings[] = { "--", "-all", "-backwards", "-count", "-elide", "-exact", - "-forwards", "-hidden", "-nocase", "-nolinestop", "-regexp", NULL + "-forwards", "-hidden", "-nocase", "-nolinestop", + "-overlap", "-regexp", "-strictlimits", NULL }; enum SearchSwitches { SEARCH_END, SEARCH_ALL, SEARCH_BACK, SEARCH_COUNT, SEARCH_ELIDE, SEARCH_EXACT, SEARCH_FWD, SEARCH_HIDDEN, SEARCH_NOCASE, - SEARCH_NOLINE, SEARCH_REGEXP + SEARCH_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP, + SEARCH_STRICTLIMITS }; /* @@ -2134,6 +2749,8 @@ TextSearchCmd(textPtr, interp, objc, objv) searchSpec.resPtr = NULL; searchSpec.searchElide = 0; searchSpec.noLineStop = 0; + searchSpec.overlap = 0; + searchSpec.strictLimits = 0; searchSpec.numLines = TkBTreeNumLines(textPtr->tree); searchSpec.clientData = (ClientData)textPtr; searchSpec.addLineProc = &TextSearchAddNextLine; @@ -2158,7 +2775,8 @@ TextSearchCmd(textPtr, interp, objc, objv) Tcl_ResetResult(interp); Tcl_AppendResult(interp, "bad switch \"", Tcl_GetString(objv[i]), "\": must be --, -all, -backward, -count, -elide, ", - "-exact, -forward, -nocase, -nolinestop, or -regexp", + "-exact, -forward, -nocase, -nolinestop, -overlap, ", + "-regexp, or -strictlimits", (char *) NULL); return TCL_ERROR; } @@ -2199,9 +2817,15 @@ TextSearchCmd(textPtr, interp, objc, objv) case SEARCH_NOCASE: searchSpec.noCase = 1; break; - case SEARCH_NOLINE: + case SEARCH_NOLINESTOP: searchSpec.noLineStop = 1; break; + case SEARCH_OVERLAP: + searchSpec.overlap = 1; + break; + case SEARCH_STRICTLIMITS: + searchSpec.strictLimits = 1; + break; case SEARCH_REGEXP: searchSpec.exact = 0; break; @@ -2224,6 +2848,12 @@ TextSearchCmd(textPtr, interp, objc, objv) return TCL_ERROR; } + if (searchSpec.overlap && !searchSpec.all) { + Tcl_SetResult(interp, "the \"-overlap\" option requires the " + "\"-all\" option to be present", TCL_STATIC); + return TCL_ERROR; + } + /* * Scan through all of the lines of the text circularly, starting * at the given index. 'objv[i]' is the pattern which may be an @@ -2487,12 +3117,17 @@ TextSearchAddNextLine(lineNum, searchSpecPtr, theLine, lenPtr) static int TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine, - matchOffset, matchLength) + matchOffset, matchLength) int lineNum; /* Line on which match was found */ SearchSpec *searchSpecPtr; /* Search parameters */ ClientData clientData; /* Token returned by the 'addNextLineProc', - * TextSearchAddNextLine */ - Tcl_Obj *theLine; /* Text from current line */ + * TextSearchAddNextLine. May be + * NULL, in which we case we must + * generate it (from lineNum) */ + Tcl_Obj *theLine; /* Text from current line, only + * accessed for exact searches, and + * is allowed to be NULL for regexp + * searches. */ int matchOffset; /* Offset of found item in utf-8 bytes * for exact search, Unicode chars * for regexp */ @@ -2544,6 +3179,10 @@ TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine, */ linePtr = (TkTextLine *)clientData; + if (linePtr == NULL) { + linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + } + curIndex.tree = textPtr->tree; curIndex.linePtr = linePtr; curIndex.byteIndex = 0; /* Find the starting point */ @@ -2844,7 +3483,7 @@ TextDumpCmd(textPtr, interp, objc, objv) arg++; atEnd = 0; if (objc == arg) { - TkTextIndexForwChars(&index1, 1, &index2); + TkTextIndexForwChars(&index1, 1, &index2, COUNT_INDICES); } else { int length; char *str; @@ -3008,8 +3647,8 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command) static int DumpSegment(interp, key, value, command, index, what) Tcl_Interp *interp; - char *key; /* Segment type key */ - char *value; /* Segment value */ + CONST char *key; /* Segment type key */ + CONST char *value; /* Segment value */ CONST char *command; /* Script callback */ CONST TkTextIndex *index; /* index with line/byte position info */ int what; /* Look for TK_DUMP_INDEX bit */ @@ -3267,7 +3906,10 @@ TextEditCmd(textPtr, interp, objc, objv) * be as simple as replacing Tcl_NewObj by Tcl_NewUnicodeObj. * * Results: - * Tcl_Obj of string type containing the specified text. + * Tcl_Obj of string type containing the specified text. If the + * visibleOnly flag is set to 1, then only those characters which + * are not elided will be returned. Otherwise (flag is 0) all + * characters in the given range are returned. * * Side effects: * Memory will be allocated for the new object. Remember to free it if @@ -3277,9 +3919,12 @@ TextEditCmd(textPtr, interp, objc, objv) */ static Tcl_Obj* -TextGetText(indexPtr1,indexPtr2) - CONST TkTextIndex *indexPtr1; - CONST TkTextIndex *indexPtr2; +TextGetText(textPtr, indexPtr1,indexPtr2, visibleOnly) + CONST TkText *textPtr; /* Information about text widget. */ + CONST TkTextIndex *indexPtr1; /* Get text from this index... */ + CONST TkTextIndex *indexPtr2; /* ...to this index */ + int visibleOnly; /* If non-zero, then only return + * non-elided characters. */ { TkTextIndex tmpIndex; Tcl_Obj *resultPtr = Tcl_NewObj(); @@ -3311,8 +3956,10 @@ TextGetText(indexPtr1,indexPtr2) } } if (segPtr->typePtr == &tkTextCharType) { - Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset, - last - offset); + if (!visibleOnly || !TkTextIsElided(textPtr, &tmpIndex)) { + Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset, + last - offset); + } } TkTextIndexForwBytes(&tmpIndex, last-offset, &tmpIndex); } @@ -3423,9 +4070,9 @@ SearchPerform(interp, searchSpecPtr, patObj, fromPtr, toPtr) /* * Scan through all of the lines of the text circularly, starting - * at the given index. 'objv[i]' is the pattern which may be an - * exact string or a regexp pattern depending on the flags set - * above. + * at the given index. 'patObj' is the pattern which may be an + * exact string or a regexp pattern depending on the flags in + * searchSpecPtr. */ return SearchCore(interp, searchSpecPtr, patObj); @@ -3482,10 +4129,27 @@ SearchCore(interp, searchSpecPtr, patObj) int lineNum = searchSpecPtr->startLine; int code = TCL_OK; Tcl_Obj *theLine; - + int alreadySearchOffset = -1; + Tcl_RegExp regexp = NULL; /* For regexp searches only */ - CONST char *pattern = NULL; /* For exact searches only */ - int firstNewLine = -1; /* For exact searches only */ + /* + * These items are for backward regexp searches only. They are for + * two purposes: to allow us to report backwards matches in the + * correct order, even though the implementation uses repeated + * forward searches; and to provide for overlap checking between + * backwards matches on different text lines. + */ +#define LOTS_OF_MATCHES 20 + int matchNum = LOTS_OF_MATCHES; + int smArray[2 * LOTS_OF_MATCHES]; + int *storeMatch = smArray; + int *storeLength = smArray + LOTS_OF_MATCHES; + int lastBackwardsLineMatch = -1; + int lastBackwardsMatchOffset = -1; + + /* These two items are for exact searches only */ + CONST char *pattern = NULL; + int firstNewLine = -1; if (searchSpecPtr->exact) { /* @@ -3521,7 +4185,7 @@ SearchCore(interp, searchSpecPtr, patObj) * will have to search across multiple lines. */ if (searchSpecPtr->exact) { - char *nl; + CONST char *nl; /* * We only need to set the matchLength once for exact searches, @@ -3571,13 +4235,31 @@ SearchCore(interp, searchSpecPtr, patObj) * 'lastOffset' (in bytes if exact, chars if regexp), since * obviously the length is the maximum offset at which * it is possible to find something on this line, which is - * what we 'lastOffset' represents. + * what 'lastOffset' represents. */ lineInfo = (*searchSpecPtr->addLineProc)(lineNum, searchSpecPtr, theLine, &lastOffset); - firstOffset = 0; + if (lineNum == searchSpecPtr->stopLine && searchSpecPtr->backwards) { + firstOffset = searchSpecPtr->stopOffset; + } else { + firstOffset = 0; + } + + if (alreadySearchOffset != -1) { + if (searchSpecPtr->backwards) { + if (alreadySearchOffset < lastOffset) { + lastOffset = alreadySearchOffset; + } + } else { + if (alreadySearchOffset > firstOffset) { + firstOffset = alreadySearchOffset; + } + } + alreadySearchOffset = -1; + } + if (lineNum == searchSpecPtr->startLine) { /* * The starting line is tricky: the first time we see it @@ -3593,18 +4275,21 @@ SearchCore(interp, searchSpecPtr, patObj) * Only use the last part of the line. */ - if ((searchSpecPtr->startOffset >= lastOffset) - && ((lastOffset != 0) || searchSpecPtr->exact)) { + if (searchSpecPtr->startOffset > firstOffset) { + firstOffset = searchSpecPtr->startOffset; + } + if ((firstOffset >= lastOffset) + && ((lastOffset != 0) || searchSpecPtr->exact)) { goto nextLine; } - - firstOffset = searchSpecPtr->startOffset; } else { /* * Use only the first part of the line. */ - lastOffset = searchSpecPtr->startOffset; + if (searchSpecPtr->startOffset < lastOffset) { + lastOffset = searchSpecPtr->startOffset; + } } } @@ -3625,18 +4310,71 @@ SearchCore(interp, searchSpecPtr, patObj) do { Tcl_UniChar ch; CONST char *p; + int lastFullLine = lastOffset; - p = strstr(startOfLine + firstOffset, pattern); - if (p == NULL) { - if (firstNewLine == -1 || - firstNewLine >= (lastOffset - firstOffset)) { + if (firstNewLine == -1) { + if (searchSpecPtr->strictLimits + && (firstOffset + matchLength > lastOffset)) { + /* Not enough characters to match. */ + break; + } + /* + * Single line matching. We want to scan forwards + * or backwards as appropriate. + */ + if (searchSpecPtr->backwards) { + /* + * Search back either from the previous match or + * from 'startOfLine + lastOffset - 1' until we + * find a match. + */ + CONST char c = pattern[0]; + if (alreadySearchOffset != -1) { + p = startOfLine + alreadySearchOffset; + alreadySearchOffset = -1; + } else { + p = startOfLine + lastOffset -1; + } + while (p >= startOfLine + firstOffset) { + if (p[0] == c && !strncmp(p, pattern, matchLength)) { + goto backwardsMatch; + } + p--; + } break; + } else { + p = strstr(startOfLine + firstOffset, pattern); } + if (p == NULL) { + /* + * Single line match failed. + */ + break; + } + } else if (firstNewLine >= (lastOffset - firstOffset)) { + /* + * Multi-line match, but not enough characters to + * match. + */ + break; + } else { + /* + * Multi-line match has only one possible match + * position, because we know where the '\n' is. + */ p = startOfLine + lastOffset - firstNewLine - 1; if (strncmp(p, pattern, (unsigned)(firstNewLine + 1))) { + /* No match */ break; } else { int extraLines = 1; + /* + * If we find a match that overlaps more than one + * line, we will use this value to determine the + * first allowed starting offset for the following + * search (to avoid overlapping results). + */ + int lastTotal = lastOffset; int skipFirst = lastOffset - firstNewLine -1; /* * We may be able to match if given more text. @@ -3644,7 +4382,7 @@ SearchCore(interp, searchSpecPtr, patObj) * exact searches. */ while (1) { - int len; + lastFullLine = lastTotal; if (lineNum+extraLines>=searchSpecPtr->numLines) { p = NULL; @@ -3658,8 +4396,11 @@ SearchCore(interp, searchSpecPtr, patObj) if (extraLines > maxExtraLines) { if ((*searchSpecPtr->addLineProc)(lineNum + extraLines, searchSpecPtr, theLine, - &len) == NULL) { + &lastTotal) == NULL) { p = NULL; + if (!searchSpecPtr->backwards) { + linesSearched = extraLines + 1; + } break; } maxExtraLines = extraLines; @@ -3671,7 +4412,7 @@ SearchCore(interp, searchSpecPtr, patObj) * Use the fact that 'matchLength = patLength' * for exact searches */ - if ((len - skipFirst) >= matchLength) { + if ((lastTotal - skipFirst) >= matchLength) { /* * We now have enough text to match, so * we make a final test and break @@ -3686,7 +4427,7 @@ SearchCore(interp, searchSpecPtr, patObj) * Not enough text yet, but check the prefix */ if (strncmp(p, pattern, - (unsigned)(len - skipFirst))) { + (unsigned)(lastTotal - skipFirst))) { p = NULL; break; } @@ -3704,44 +4445,77 @@ SearchCore(interp, searchSpecPtr, patObj) if (p == NULL) { break; } + linesSearched = extraLines; } } - firstOffset = p - startOfLine; - if (firstOffset >= lastOffset) { + backwardsMatch: + if ((p - startOfLine) >= lastOffset) { break; } - /* * Remember the match */ - matchOffset = firstOffset; + matchOffset = p - startOfLine; - /* - * Move the starting point one character on from the - * previous match, in case we are doing repeated or - * backwards searches (for the latter, we actually do - * repeated forward searches). - */ - firstOffset += Tcl_UtfToUniChar(startOfLine+matchOffset, &ch); if (searchSpecPtr->all && - !(*searchSpecPtr->foundMatchProc)(lineNum, - searchSpecPtr, lineInfo, theLine, matchOffset, - matchLength)) { + !(*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength)) { /* * We reached the end of the search */ goto searchDone; } - } while (searchSpecPtr->backwards || searchSpecPtr->all); + if (!searchSpecPtr->overlap) { + if (searchSpecPtr->backwards) { + alreadySearchOffset = p - startOfLine; + if (firstNewLine != -1) { + break; + } else { + alreadySearchOffset -= matchLength; + } + } else { + firstOffset = p - startOfLine + matchLength; + if (firstOffset >= lastOffset) { + /* + * Now, we have to be careful not to find + * overlapping matches either on the same or + * following lines. Assume that if we did find + * something, it goes until the last extra line + * we added. + * + * We can break out of the loop, since we know + * no more will be found. + */ + if (!searchSpecPtr->backwards) { + alreadySearchOffset = firstOffset - lastFullLine; + break; + } + } + } + } else { + if (searchSpecPtr->backwards) { + alreadySearchOffset = p - startOfLine - 1; + if (alreadySearchOffset < 0) { + break; + } + } else { + firstOffset = p - startOfLine + + Tcl_UtfToUniChar(startOfLine + matchOffset, &ch); + } + } + } while (searchSpecPtr->all); } else { int maxExtraLines = 0; - + int matches = 0; + int lastNonOverlap = -1; + do { Tcl_RegExpInfo info; int match; - + int lastFullLine = lastOffset; + match = Tcl_RegExpExecObj(interp, regexp, theLine, firstOffset, 1, ((firstOffset > 0) ? TCL_REG_NOTBOL : 0)); if (match < 0) { @@ -3750,24 +4524,45 @@ SearchCore(interp, searchSpecPtr, patObj) } Tcl_RegExpGetInfo(regexp, &info); - if (!match) { + /* + * If we don't have a match, or if we do, but it + * extends to the end of the line, we must try to + * add more lines to get a full greedy match. + */ + if (!match + || ((info.extendStart == info.matches[0].start) + && (info.matches[0].end == lastOffset))) { int extraLines = 1; - int curLen = 0; + int prevFullLine; + /* + * If we find a match that overlaps more than one + * line, we will use this value to determine the + * first allowed starting offset for the following + * search (to avoid overlapping results). + */ + int lastTotal = lastOffset; + + if ((lastBackwardsLineMatch != -1) + && (lastBackwardsLineMatch == (lineNum + 1))) { + lastNonOverlap = lastTotal; + } if (info.extendStart < 0) { + /* No multi-line match is possible */ break; } /* * We may be able to match if given more text. * The following 'while' block handles multi-line - * exact searches. + * regexp searches. */ while (1) { + prevFullLine = lastTotal; /* * Move firstOffset to first possible start */ - firstOffset += info.extendStart; + if (!match) firstOffset += info.extendStart; if (firstOffset >= lastOffset) { /* * We're being told that the only possible @@ -3775,8 +4570,8 @@ SearchCore(interp, searchSpecPtr, patObj) * the line. But, that is the next line which * we will handle when we look at that line. */ - if (!searchSpecPtr->backwards - && (firstOffset == curLen)) { + if (!match && !searchSpecPtr->backwards + && (firstOffset == 0)) { linesSearched = extraLines + 1; } break; @@ -3791,17 +4586,22 @@ SearchCore(interp, searchSpecPtr, patObj) if (extraLines > maxExtraLines) { if ((*searchSpecPtr->addLineProc)(lineNum + extraLines, searchSpecPtr, theLine, - NULL) == NULL) { + &lastTotal) == NULL) { /* * There are no more acceptable lines, so * we can say we have searched all of these */ - if (!searchSpecPtr->backwards) { + if (!match && !searchSpecPtr->backwards) { linesSearched = extraLines + 1; } break; } maxExtraLines = extraLines; + if ((lastBackwardsLineMatch != -1) + && (lastBackwardsLineMatch + == (lineNum + extraLines + 1))) { + lastNonOverlap = lastTotal; + } } match = Tcl_RegExpExecObj(interp, regexp, theLine, @@ -3812,64 +4612,286 @@ SearchCore(interp, searchSpecPtr, patObj) goto searchDone; } Tcl_RegExpGetInfo(regexp, &info); - if (match || info.extendStart < 0) { + + /* + * Unfortunately there are bugs in Tcl's regexp + * library, which tells us that info.extendStart + * is zero when it should not be (should be -1), + * which makes our task a bit more complicated + * here. We check if there was a match, and the + * end of the match leaves an entire extra line + * unmatched, then we stop searching. Clearly it + * still might sometimes be possible to add more + * text and match again, but Tcl's regexp library + * doesn't tell us that. + * + * This means we often add and search one more + * line than might be necessary if Tcl were able + * to give us a correct value of info.extendStart + * under all circumstances. + */ + if ((match && ((firstOffset + info.matches[0].end) + != lastTotal) + && ((firstOffset + info.matches[0].end) + < prevFullLine)) + || info.extendStart < 0) { break; } + if (match && ((firstOffset + info.matches[0].end) + >= prevFullLine)) { + linesSearched = extraLines; + lastFullLine = prevFullLine; + } /* * The prefix matches, so keep looking */ extraLines++; } /* - * If we reach here, with match == 1, we've found a + * If we reach here with 'match == 1', we've found a * multi-line match, which we will record in the code * which follows directly else we started a * multi-line match but didn't finish it off, so we * go to the next line. - * - * Here is where we could perform an optimisation, - * since we have already retrieved the contents of - * the next line (and many more), so we shouldn't - * really throw it all away and start again. This - * could be particularly important for complex regexp - * searches. */ if (!match) { - /* - * This 'break' will take us to just before - * the 'nextLine:' below. + /* + * Here is where we could perform an optimisation, + * since we have already retrieved the contents of + * the next line (perhaps many more), so we shouldn't + * really throw it all away and start again. This + * could be particularly important for complex regexp + * searches. + * + * This 'break' will take us to just before the + * 'nextLine:' below. */ break; } - } + if (lastBackwardsLineMatch != -1) { + if ((lineNum + linesSearched) + == lastBackwardsLineMatch) { + /* Possible overlap or inclusion */ + int thisOffset = firstOffset + info.matches[0].end + - info.matches[0].start; + + if (lastNonOverlap != -1) { + /* Possible overlap or enclosure */ + if ((thisOffset - lastNonOverlap) + >= (lastBackwardsMatchOffset + + matchLength)) { + /* + * Totally encloses previous match, so + * forget the previous match + */ + lastBackwardsLineMatch = -1; + } else if ((thisOffset - lastNonOverlap) + > lastBackwardsMatchOffset) { + /* + * Overlap. Previous match is ok, and + * the current match is only ok if + * we are searching with -overlap. + */ + if (searchSpecPtr->overlap) { + goto recordBackwardsMatch; + } else { + match = 0; + break; + } + } else { + /* + * No overlap, although the same + * line was reached. + */ + goto recordBackwardsMatch; + } + } else { + /* No overlap */ + goto recordBackwardsMatch; + } + } else if (lineNum + linesSearched + < lastBackwardsLineMatch) { + /* No overlap */ + goto recordBackwardsMatch; + } else { + /* Totally enclosed */ + lastBackwardsLineMatch = -1; + } + } + + } else { + /* Matched in a single line */ + if (lastBackwardsLineMatch != -1) { + recordBackwardsMatch: + (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch, + searchSpecPtr, NULL, NULL, + lastBackwardsMatchOffset, matchLength); + lastBackwardsLineMatch = -1; + if (!searchSpecPtr->all) { + goto searchDone; + } + } + } + firstOffset += info.matches[0].start; if (firstOffset >= lastOffset) { break; } /* - * Remember the match + * Update our local variables with the match, if we + * haven't yet found anything, or if we're doing '-all' + * or '-backwards' _and_ this match isn't fully enclosed + * in the previous match. */ - matchOffset = firstOffset; - matchLength = info.matches[0].end - info.matches[0].start; + + if (matchOffset == -1 || + ((searchSpecPtr->all || searchSpecPtr->backwards) + && ((firstOffset < matchOffset) + || ((firstOffset + info.matches[0].end + - info.matches[0].start) + > (matchOffset + matchLength)) + ) + ) + ) { + + matchOffset = firstOffset; + matchLength = info.matches[0].end - info.matches[0].start; + + if (searchSpecPtr->backwards) { + /* + * To get backwards searches in the correct + * order, we must store them away here. + */ + if (matches == matchNum) { + /* + * We've run out of space in our normal + * store, so we must allocate space for + * these backwards matches on the heap. + */ + int *newArray; + newArray = (int*) ckalloc(4*matchNum *sizeof(int)); + memcpy(newArray, storeMatch, matchNum*sizeof(int)); + memcpy(newArray + 2*matchNum, + storeLength, matchNum*sizeof(int)); + if (storeMatch != smArray) { + ckfree((char*)storeMatch); + } + matchNum *= 2; + storeMatch = newArray; + storeLength = newArray + matchNum; + } + storeMatch[matches] = matchOffset; + storeLength[matches] = matchLength; + matches++; + } else { + /* + * Now actually record the match, but only if we + * are doing an '-all' search. + */ + if (searchSpecPtr->all && + !(*searchSpecPtr->foundMatchProc)(lineNum, + searchSpecPtr, lineInfo, theLine, matchOffset, + matchLength)) { + /* + * We reached the end of the search + */ + goto searchDone; + } + } + /* + * For forward matches, unless we allow overlaps, we + * move this on by the length of the current match so + * that we explicitly disallow overlapping matches. + */ + if (matchLength > 0 && !searchSpecPtr->overlap + && !searchSpecPtr->backwards) { + firstOffset += matchLength; + if (firstOffset >= lastOffset) { + /* + * Now, we have to be careful not to find + * overlapping matches either on the same or + * following lines. Assume that if we did find + * something, it goes until the last extra line + * we added. + * + * We can break out of the loop, since we know + * no more will be found. + */ + alreadySearchOffset = firstOffset - lastFullLine; + break; + } + /* We'll add this on again just below */ + firstOffset --; + } + } /* - * Move the starting point one character on, in case - * we are doing repeated or backwards searches (for the - * latter, we actually do repeated forward searches). + * Move the starting point on, in case we are doing + * repeated or backwards searches (for the latter, we + * actually do repeated forward searches). */ firstOffset++; - if (searchSpecPtr->all && - !(*searchSpecPtr->foundMatchProc)(lineNum, - searchSpecPtr, lineInfo, theLine, matchOffset, - matchLength)) { - /* - * We reached the end of the search + } while (searchSpecPtr->backwards || searchSpecPtr->all); + + if (matches > 0) { + /* + * Now we have all the matches in our array, but not stored + * with 'foundMatchProc' yet. + */ + matches--; + matchOffset = storeMatch[matches]; + matchLength = storeLength[matches]; + while (--matches >= 0) { + if (lineNum == searchSpecPtr->stopLine) { + /* + * It appears as if a condition like + * 'if (storeMatch[matches] + * < searchSpecPtr->stopOffset) break;' + * + * might be needed here, but no test case + * has been found which would exercise such + * a problem. + */ + } + if (storeMatch[matches] + storeLength[matches] + >= matchOffset + matchLength) { + /* + * The new match totally encloses the previous + * one, so we overwrite the previous one. + */ + matchOffset = storeMatch[matches]; + matchLength = storeLength[matches]; + continue; + } + if (!searchSpecPtr->overlap) { + if (storeMatch[matches] + storeLength[matches] + > matchOffset) { + continue; + } + } + (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength); + if (!searchSpecPtr->all) { + goto searchDone; + } + matchOffset = storeMatch[matches]; + matchLength = storeLength[matches]; + } + if (searchSpecPtr->all && matches > 0) { + /* + * We only need to do this for the '-all' case, + * because just below we will call the + * foundMatchProc for the non-all case */ - goto searchDone; + (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength); + } else { + lastBackwardsLineMatch = lineNum; + lastBackwardsMatchOffset = matchOffset; } - } while (searchSpecPtr->backwards || searchSpecPtr->all); + } } /* @@ -3880,9 +4902,11 @@ SearchCore(interp, searchSpecPtr, patObj) * and we are done. */ - if ((matchOffset >= 0) && !searchSpecPtr->all) { - (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, - lineInfo, theLine, matchOffset, matchLength); + if ((lastBackwardsLineMatch == -1) + && (matchOffset >= 0) + && !searchSpecPtr->all) { + (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength); goto searchDone; } @@ -3892,7 +4916,7 @@ SearchCore(interp, searchSpecPtr, patObj) nextLine: - for (; linesSearched>0 ; linesSearched--) { + while (linesSearched-- > 0) { /* * If we have just completed the 'stopLine', we are done */ @@ -3902,26 +4926,71 @@ SearchCore(interp, searchSpecPtr, patObj) if (searchSpecPtr->backwards) { lineNum--; + + if (lastBackwardsLineMatch != -1 + && ((lineNum < 0) || (lineNum + 2 < lastBackwardsLineMatch))) { + (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch, + searchSpecPtr, NULL, NULL, + lastBackwardsMatchOffset, + matchLength); + lastBackwardsLineMatch = -1; + if (!searchSpecPtr->all) { + goto searchDone; + } + } + if (lineNum < 0) { lineNum = searchSpecPtr->numLines-1; } + if (!searchSpecPtr->exact) { + /* + * The 'exact' search loops above are designed to + * give us an accurate picture of the number of lines + * which we can skip here. For 'regexp' searches, on + * the other hand, which can match potentially variable + * lengths, we cannot skip multiple lines when searching + * backwards. Therefore we only allow one line to be + * skipped here. + */ + break; + } } else { lineNum++; if (lineNum >= searchSpecPtr->numLines) { lineNum = 0; } } + if (lineNum == searchSpecPtr->startLine && linesSearched > 0) { + /* + * We've just searched all the way round and have + * gone right through the start line without finding + * anything in the last attempt. + */ + break; + } } Tcl_SetObjLength(theLine, 0); } searchDone: + if (lastBackwardsLineMatch != -1) { + (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch, searchSpecPtr, + NULL, NULL, lastBackwardsMatchOffset, matchLength); + } + /* * Free up the cached line and pattern */ Tcl_DecrRefCount(theLine); Tcl_DecrRefCount(patObj); + /* + * Free up any extra space we allocated + */ + if (storeMatch != smArray) { + ckfree((char*)storeMatch); + } + return code; } diff --git a/generic/tkText.h b/generic/tkText.h index efe872d..f59f003 100644 --- a/generic/tkText.h +++ b/generic/tkText.h @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkText.h,v 1.16 2003/09/29 23:15:20 dkf Exp $ + * RCS: @(#) $Id: tkText.h,v 1.17 2003/10/31 09:02:09 vincentdarley Exp $ */ #ifndef _TKTEXT @@ -37,8 +37,9 @@ typedef struct TkTextBTree_ *TkTextBTree; /* - * The data structure below defines a single line of text (from newline - * to newline, not necessarily what appears on one line of the screen). + * The data structure below defines a single logical line of text (from + * newline to newline, not necessarily what appears on one display line + * of the screen). */ typedef struct TkTextLine { @@ -49,6 +50,15 @@ typedef struct TkTextLine { * means end of list. */ struct TkTextSegment *segPtr; /* First in ordered list of segments * that make up the line. */ + int pixelHeight; /* The number of vertical + * pixels taken up by this + * line, whether currently + * displayed or not. This + * number is only updated + * asychronously. */ + int pixelCalculationEpoch; /* The last epoch at which the + * pixel height was + * recalculated. */ } TkTextLine; /* @@ -148,6 +158,8 @@ typedef struct TkTextEmbImage { * of image, in pixels. */ int chunkCount; /* Number of display chunks that * refer to this image. */ + Tk_OptionTable optionTable; /* Token representing the + * configuration specifications. */ } TkTextEmbImage; /* @@ -189,7 +201,11 @@ typedef struct TkTextIndex { * of interest. */ int byteIndex; /* Index within line of desired * character (0 means first one). */ - struct TkText *textPtr; + struct TkText *textPtr; /* May be NULL, but otherwise + * the text widget with which + * this index is associated. + * If not NULL, then we have a + * refCount on the widget. */ } TkTextIndex; /* @@ -286,7 +302,7 @@ typedef enum { TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, } TkWrapMode; typedef struct TkTextTag { - char *name; /* Name of this tag. This field is actually + CONST char *name; /* Name of this tag. This field is actually * a pointer to the key from the entry in * textPtr->tagTable, so it needn't be freed * explicitly. */ @@ -395,6 +411,10 @@ typedef struct TkTextTag { * (so need to redisplay if tag changes). */ Tk_OptionTable optionTable; /* Token representing the configuration * specifications. */ + int affectsDisplayGeometry; /* Non-zero means that this tag affects the + * size with which information is displayed + * on the screen (so need to recalculate line + * dimensions if tag changes). */ } TkTextTag; #define TK_TAG_AFFECTS_DISPLAY 0x1 @@ -464,6 +484,7 @@ typedef struct TkTextTabArray { typedef enum { TK_TEXT_EDIT_INSERT, /* insert mode */ TK_TEXT_EDIT_DELETE, /* delete mode */ + TK_TEXT_EDIT_REPLACE, /* replace mode */ TK_TEXT_EDIT_OTHER /* none of the above */ } TkTextEditMode; @@ -537,6 +558,8 @@ typedef struct TkText { Tk_Font tkfont; /* Default font for displaying text. */ int charWidth; /* Width of average character in default * font. */ + int charHeight; /* Height of average character in default + * font, including line spacing. */ int spacing1; /* Default extra spacing above first display * line for each text line. */ int spacing2; /* Default extra spacing between display lines @@ -687,9 +710,10 @@ typedef struct TkText { int isDirtyIncrement; /* Amount with which the isDirty flag is * incremented every edit action */ - TkTextEditMode lastEditMode;/* Keeps track of what the last edit mode was - */ - + TkTextEditMode lastEditMode;/* Keeps track of what the last edit + * mode was */ + int insertCursorType; /* 0 = standard insertion cursor, + * 1 = block cursor. */ } TkText; /* @@ -745,7 +769,7 @@ typedef void Tk_SegCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); typedef struct Tk_SegType { - char *name; /* Name of this kind of segment. */ + CONST char *name; /* Name of this kind of segment. */ int leftGravity; /* If a segment has zero size (e.g. a * mark or tag toggle), does it * attach to character to its left @@ -777,6 +801,23 @@ typedef struct Tk_SegType { * segment. */ } Tk_SegType; + +/* + * The following type and items describe different flags for text widget + * items to count. They are used in both tkText.c and tkTextIndex.c, + * in 'CountIndices', 'TkTextIndexBackChars', 'TkTextIndexForwChars', + * and 'TkTextIndexCount'. + */ + +typedef int TkTextCountType; + +#define COUNT_CHARS 0 +#define COUNT_INDICES 1 +#define COUNT_DISPLAY 2 +#define COUNT_IS_ELIDED 4 +#define COUNT_DISPLAY_CHARS (COUNT_CHARS | COUNT_DISPLAY) +#define COUNT_DISPLAY_INDICES (COUNT_INDICES | COUNT_DISPLAY) + /* * The constant below is used to specify a line when what is really * wanted is the entire text. For now, just use a very big number. @@ -791,6 +832,26 @@ typedef struct Tk_SegType { #define TK_POS_CHARS 30 +/* + * Mask used for those options which may impact the pixel height + * calculations of individual lines displayed in the widget. + */ +#define TK_TEXT_LINE_GEOMETRY 1 + +/* + * Used as 'action' values in calls to TkTextInvalidateLineMetrics + */ +#define TK_TEXT_INVALIDATE_ONLY 0 +#define TK_TEXT_INVALIDATE_INSERT 1 +#define TK_TEXT_INVALIDATE_DELETE 2 + +/* + * Used as special 'pickPlace' values in calls to TkTextSetYView. + * Zero or positive values indicate a number of pixels. + */ +#define TK_TEXT_PICKPLACE -1 +#define TK_TEXT_NOPIXELADJUST -2 + /* * Declarations for variables shared among the text-related files: */ @@ -808,6 +869,8 @@ EXTERN Tk_SegType tkTextToggleOffType; * but shouldn't be used anywhere else in Tk (or by Tk clients): */ +EXTERN int TkBTreeAdjustPixelHeight _ANSI_ARGS_((TkTextLine *linePtr, + int newPixelHeight)); EXTERN int TkBTreeCharTagged _ANSI_ARGS_((CONST TkTextIndex *indexPtr, TkTextTag *tagPtr)); EXTERN void TkBTreeCheck _ANSI_ARGS_((TkTextBTree tree)); @@ -819,16 +882,20 @@ EXTERN void TkBTreeDeleteChars _ANSI_ARGS_((TkTextIndex *index1Ptr, TkTextIndex *index2Ptr)); EXTERN TkTextLine * TkBTreeFindLine _ANSI_ARGS_((TkTextBTree tree, int line)); +EXTERN TkTextLine * TkBTreeFindPixelLine _ANSI_ARGS_((TkTextBTree tree, + int pixels, int *pixelOffset)); EXTERN TkTextTag ** TkBTreeGetTags _ANSI_ARGS_((CONST TkTextIndex *indexPtr, int *numTagsPtr)); EXTERN void TkBTreeInsertChars _ANSI_ARGS_((TkTextIndex *indexPtr, CONST char *string)); EXTERN int TkBTreeLineIndex _ANSI_ARGS_((TkTextLine *linePtr)); +EXTERN int TkBTreePixels _ANSI_ARGS_((TkTextLine *linePtr)); EXTERN void TkBTreeLinkSegment _ANSI_ARGS_((TkTextSegment *segPtr, TkTextIndex *indexPtr)); EXTERN TkTextLine * TkBTreeNextLine _ANSI_ARGS_((TkTextLine *linePtr)); EXTERN int TkBTreeNextTag _ANSI_ARGS_((TkTextSearch *searchPtr)); EXTERN int TkBTreeNumLines _ANSI_ARGS_((TkTextBTree tree)); +EXTERN int TkBTreeNumPixels _ANSI_ARGS_((TkTextBTree tree)); EXTERN TkTextLine * TkBTreePreviousLine _ANSI_ARGS_((TkTextLine *linePtr)); EXTERN int TkBTreePrevTag _ANSI_ARGS_((TkTextSearch *searchPtr)); EXTERN void TkBTreeStartSearch _ANSI_ARGS_((TkTextIndex *index1Ptr, @@ -837,7 +904,7 @@ EXTERN void TkBTreeStartSearch _ANSI_ARGS_((TkTextIndex *index1Ptr, EXTERN void TkBTreeStartSearchBack _ANSI_ARGS_((TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, TkTextSearch *searchPtr)); -EXTERN void TkBTreeTag _ANSI_ARGS_((TkTextIndex *index1Ptr, +EXTERN int TkBTreeTag _ANSI_ARGS_((TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, int add)); EXTERN void TkBTreeUnlinkSegment _ANSI_ARGS_((TkTextBTree tree, @@ -845,10 +912,11 @@ EXTERN void TkBTreeUnlinkSegment _ANSI_ARGS_((TkTextBTree tree, EXTERN void TkTextBindProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); EXTERN void TkTextChanged _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *index1Ptr, TkTextIndex *index2Ptr)); + CONST TkTextIndex *index1Ptr, + CONST TkTextIndex *index2Ptr)); EXTERN int TkTextCharBbox _ANSI_ARGS_((TkText *textPtr, CONST TkTextIndex *indexPtr, int *xPtr, int *yPtr, - int *widthPtr, int *heightPtr)); + int *widthPtr, int *heightPtr, int *charWidthPtr)); EXTERN int TkTextCharLayoutProc _ANSI_ARGS_((TkText *textPtr, TkTextIndex *indexPtr, TkTextSegment *segPtr, int offset, int maxX, int maxChars, int noBreakYet, @@ -857,6 +925,10 @@ EXTERN void TkTextCreateDInfo _ANSI_ARGS_((TkText *textPtr)); EXTERN int TkTextDLineInfo _ANSI_ARGS_((TkText *textPtr, CONST TkTextIndex *indexPtr, int *xPtr, int *yPtr, int *widthPtr, int *heightPtr, int *basePtr)); +EXTERN void TkTextEmbWinDisplayProc _ANSI_ARGS_(( + TkTextDispChunk *chunkPtr, int x, int y, + int lineHeight, int baseline, Display *display, + Drawable dst, int screenY)); EXTERN TkTextTag * TkTextCreateTag _ANSI_ARGS_((TkText *textPtr, CONST char *tagName)); EXTERN void TkTextFreeDInfo _ANSI_ARGS_((TkText *textPtr)); @@ -872,21 +944,32 @@ EXTERN CONST TkTextIndex* TkTextGetIndexFromObj _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *objPtr)); EXTERN TkTextTabArray * TkTextGetTabs _ANSI_ARGS_((Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *stringPtr)); +EXTERN void TkTextFindDisplayLineEnd _ANSI_ARGS_(( + TkText *textPtr, TkTextIndex *indexPtr, + int end, int *xOffset)); EXTERN void TkTextIndexBackBytes _ANSI_ARGS_(( CONST TkTextIndex *srcPtr, int count, TkTextIndex *dstPtr)); EXTERN void TkTextIndexBackChars _ANSI_ARGS_(( CONST TkTextIndex *srcPtr, int count, - TkTextIndex *dstPtr)); + TkTextIndex *dstPtr, TkTextCountType type)); EXTERN int TkTextIndexCmp _ANSI_ARGS_(( CONST TkTextIndex *index1Ptr, CONST TkTextIndex *index2Ptr)); -EXTERN void TkTextIndexForwBytes _ANSI_ARGS_(( +EXTERN int TkTextIndexCount _ANSI_ARGS_((CONST TkText *textPtr, + CONST TkTextIndex *index1Ptr, + CONST TkTextIndex *index2Ptr, + TkTextCountType type)); +EXTERN int TkTextIndexForwBytes _ANSI_ARGS_(( CONST TkTextIndex *srcPtr, int count, TkTextIndex *dstPtr)); EXTERN void TkTextIndexForwChars _ANSI_ARGS_(( CONST TkTextIndex *srcPtr, int count, - TkTextIndex *dstPtr)); + TkTextIndex *dstPtr, TkTextCountType type)); +EXTERN void TkTextIndexOfX _ANSI_ARGS_((TkText *textPtr, + int x, TkTextIndex *indexPtr)); +EXTERN int TkTextIndexYPixels _ANSI_ARGS_((TkText *textPtr, + CONST TkTextIndex *indexPtr)); EXTERN TkTextSegment * TkTextIndexToSeg _ANSI_ARGS_(( CONST TkTextIndex *indexPtr, int *offsetPtr)); EXTERN void TkTextInsertDisplayProc _ANSI_ARGS_(( @@ -898,11 +981,25 @@ EXTERN void TkTextLostSelection _ANSI_ARGS_(( EXTERN TkTextIndex * TkTextMakeCharIndex _ANSI_ARGS_((TkTextBTree tree, int lineIndex, int charIndex, TkTextIndex *indexPtr)); -EXTERN int TkTextIsElided _ANSI_ARGS_((TkText *textPtr, +EXTERN int TkTextMeasureDown _ANSI_ARGS_((TkText *textPtr, + TkTextIndex *srcPtr, int distance)); +EXTERN int TkTextIsElided _ANSI_ARGS_((CONST TkText *textPtr, CONST TkTextIndex *indexPtr)); EXTERN TkTextIndex * TkTextMakeByteIndex _ANSI_ARGS_((TkTextBTree tree, int lineIndex, int byteIndex, TkTextIndex *indexPtr)); +EXTERN int TkTextMakePixelIndex _ANSI_ARGS_((TkText *textPtr, + int pixelIndex, TkTextIndex *indexPtr)); +EXTERN void TkTextInvalidateLineMetrics _ANSI_ARGS_((TkText *textPtr, + TkTextLine *linePtr, int lineCount, int action)); +EXTERN void TkTextAsyncUpdateLineMetrics _ANSI_ARGS_((ClientData + clientData)); +EXTERN int TkTextUpdateLineMetrics _ANSI_ARGS_((TkText *textPtr, + int lineNum, int endLine, int doThisMuch)); +EXTERN int TkTextUpdateOneLine _ANSI_ARGS_((TkText *textPtr, + TkTextLine *linePtr)); +EXTERN void TkTextUpdateYScrollbar _ANSI_ARGS_((ClientData + clientData)); EXTERN int TkTextMarkCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); EXTERN int TkTextMarkNameToIndex _ANSI_ARGS_((TkText *textPtr, @@ -923,7 +1020,8 @@ EXTERN void TkTextRedrawRegion _ANSI_ARGS_((TkText *textPtr, EXTERN void TkTextRedrawTag _ANSI_ARGS_((TkText *textPtr, TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, int withTag)); -EXTERN void TkTextRelayoutWindow _ANSI_ARGS_((TkText *textPtr)); +EXTERN void TkTextRelayoutWindow _ANSI_ARGS_((TkText *textPtr, + int mask)); EXTERN int TkTextScanCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); EXTERN int TkTextSeeCmd _ANSI_ARGS_((TkText *textPtr, @@ -949,9 +1047,6 @@ EXTERN int TkTextXviewCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); EXTERN int TkTextYviewCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); -/* Use a helper from tkCanvas.c */ -EXTERN CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc, - Tcl_Obj *CONST *objv)); # undef TCL_STORAGE_CLASS # define TCL_STORAGE_CLASS DLLIMPORT diff --git a/generic/tkTextBTree.c b/generic/tkTextBTree.c index 762d5f3..63d10c0 100644 --- a/generic/tkTextBTree.c +++ b/generic/tkTextBTree.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextBTree.c,v 1.7 2003/05/19 13:04:23 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextBTree.c,v 1.8 2003/10/31 09:02:10 vincentdarley Exp $ */ #include "tkInt.h" @@ -55,6 +55,7 @@ typedef struct Node { int numChildren; /* Number of children of this node. */ int numLines; /* Total number of lines (leaves) in * the subtree rooted here. */ + int numPixels; } Node; /* @@ -217,7 +218,7 @@ TkBTreeCreate(textPtr) register Node *rootPtr; register TkTextLine *linePtr, *linePtr2; register 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 @@ -235,7 +236,13 @@ TkBTreeCreate(textPtr) rootPtr->children.linePtr = linePtr; rootPtr->numChildren = 2; rootPtr->numLines = 2; - + rootPtr->numPixels = textPtr->charHeight; + linePtr->pixelHeight = textPtr->charHeight; + linePtr->pixelCalculationEpoch = 0; + /* The last line permanently has a pixel height of zero */ + linePtr2->pixelHeight = 0; + linePtr2->pixelCalculationEpoch = 1; + linePtr->parentPtr = rootPtr; linePtr->nextPtr = linePtr2; segPtr = (TkTextSegment *) ckalloc(CSEG_SIZE(1)); @@ -245,7 +252,7 @@ TkBTreeCreate(textPtr) 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)); @@ -371,6 +378,54 @@ DeleteSummaries(summaryPtr) /* *---------------------------------------------------------------------- * + * 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(linePtr, newPixelHeight) + register TkTextLine *linePtr; /* The logical line to update */ + int newPixelHeight; /* The line's known height + * in pixels */ +{ + register Node *nodePtr; + int changeToPixelCount; /* Counts change to total number of + * pixels in file. */ + + changeToPixelCount = newPixelHeight - linePtr->pixelHeight; + + /* + * Increment the pixel counts in all the parent nodes of the + * current line, then rebalance the tree if necessary. + */ + + nodePtr = linePtr->parentPtr; + nodePtr->numPixels += changeToPixelCount; + + while (nodePtr->parentPtr != NULL) { + nodePtr = nodePtr->parentPtr; + nodePtr->numPixels += changeToPixelCount; + } + + linePtr->pixelHeight = newPixelHeight; + + return nodePtr->numPixels; +} + +/* + *---------------------------------------------------------------------- + * * TkBTreeInsertChars -- * * Insert characters at a given position in a B-tree. @@ -414,6 +469,8 @@ TkBTreeInsertChars(indexPtr, string) * 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. */ prevPtr = SplitSeg(indexPtr); linePtr = indexPtr->linePtr; @@ -426,6 +483,7 @@ TkBTreeInsertChars(indexPtr, string) */ changeToLineCount = 0; + changeToPixelCount = 0; while (*string != 0) { for (eol = string; *eol != 0; eol++) { if (*eol == '\n') { @@ -461,11 +519,19 @@ TkBTreeInsertChars(indexPtr, string) newLinePtr->nextPtr = linePtr->nextPtr; linePtr->nextPtr = newLinePtr; newLinePtr->segPtr = segPtr->nextPtr; + /* + * Set up a starting default height, which will be re-adjusted + * later + */ + newLinePtr->pixelHeight = linePtr->pixelHeight; + newLinePtr->pixelCalculationEpoch = 0; + segPtr->nextPtr = NULL; linePtr = newLinePtr; curPtr = NULL; changeToLineCount++; - + changeToPixelCount += newLinePtr->pixelHeight; + string = eol; } @@ -478,15 +544,26 @@ TkBTreeInsertChars(indexPtr, string) if (linePtr != indexPtr->linePtr) { CleanupLine(linePtr); } + + /* + * 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-calculated the line height of the last line). + */ + TkTextInvalidateLineMetrics(((BTree*)indexPtr->tree)->textPtr, + indexPtr->linePtr, changeToLineCount, + TK_TEXT_INVALIDATE_INSERT); /* - * Increment the line counts in all the parent nodes of the insertion - * point, then rebalance the tree if necessary. + * 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; + nodePtr->numPixels += changeToPixelCount; } nodePtr = linePtr->parentPtr; nodePtr->numChildren += changeToLineCount; @@ -649,7 +726,8 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) TkTextSegment *segPtr, *nextPtr; TkTextLine *curLinePtr; Node *curNodePtr, *nodePtr; - + int changeToLineCount = 0; + /* * Tricky point: split at index2Ptr first; otherwise the split * at index2Ptr may invalidate segPtr and/or prevPtr. @@ -675,6 +753,7 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) */ curLinePtr = index1Ptr->linePtr; + curNodePtr = curLinePtr->parentPtr; while (segPtr != lastPtr) { if (segPtr == NULL) { @@ -696,7 +775,9 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) for (nodePtr = curNodePtr; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines--; + nodePtr->numPixels -= curLinePtr->pixelHeight; } + changeToLineCount++; curNodePtr->numChildren--; ckfree((char *) curLinePtr); } @@ -768,7 +849,9 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) for (nodePtr = curNodePtr; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines--; + nodePtr->numPixels -= index2Ptr->linePtr->pixelHeight; } + changeToLineCount++; curNodePtr->numChildren--; prevLinePtr = curNodePtr->children.linePtr; if (prevLinePtr == index2Ptr->linePtr) { @@ -780,6 +863,7 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) prevLinePtr->nextPtr = index2Ptr->linePtr->nextPtr; } ckfree((char *) index2Ptr->linePtr); + Rebalance((BTree *) index2Ptr->tree, curNodePtr); } @@ -790,6 +874,18 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) 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(index1Ptr->linePtr) != NULL) { + TkTextInvalidateLineMetrics(((BTree*)index1Ptr->tree)->textPtr, + index1Ptr->linePtr, changeToLineCount, + TK_TEXT_INVALIDATE_DELETE); + } + + /* * Lastly, rebalance the first node of the range. */ @@ -865,6 +961,82 @@ TkBTreeFindLine(tree, line) /* *---------------------------------------------------------------------- * + * 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(tree, pixels, pixelOffset) + TkTextBTree tree; /* B-tree in which to find line. */ + int pixels; /* Index of desired line. */ + int *pixelOffset; /* Used to return offset */ +{ + BTree *treePtr = (BTree *) tree; + register Node *nodePtr; + register TkTextLine *linePtr; + int pixelsLeft; + + nodePtr = treePtr->rootPtr; + pixelsLeft = pixels; + + if ((pixels < 0) || (pixels > nodePtr->numPixels)) { + 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->numPixels <= pixelsLeft; + nodePtr = nodePtr->nextPtr) { + if (nodePtr == NULL) { + panic("TkBTreeFindLine ran out of nodes"); + } + pixelsLeft -= nodePtr->numPixels; + } + } + + /* + * Work through the lines attached to the level-0 node. + */ + + for (linePtr = nodePtr->children.linePtr; + linePtr->pixelHeight < pixelsLeft; + linePtr = linePtr->nextPtr) { + if (linePtr == NULL) { + panic("TkBTreeFindLine ran out of lines"); + } + pixelsLeft -= linePtr->pixelHeight; + } + if (pixelOffset != NULL && linePtr != NULL) { + *pixelOffset = pixelsLeft; + } + return linePtr; +} + +/* + *---------------------------------------------------------------------- + * * TkBTreeNextLine -- * * Given an existing line in a B-tree, this procedure locates the @@ -988,6 +1160,72 @@ TkBTreePreviousLine(linePtr) /* *---------------------------------------------------------------------- * + * TkBTreePixels -- + * + * 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 index of linePtr within the tree, where 0 + * corresponds to the first line in the tree. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TkBTreePixels(linePtr) + TkTextLine *linePtr; /* Pointer to existing line in + * B-tree. */ +{ + register TkTextLine *linePtr2; + register Node *nodePtr, *parentPtr, *nodePtr2; + int index; + + /* + * First count how many lines precede this one in its level-0 + * node. + */ + + nodePtr = linePtr->parentPtr; + index = 0; + for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr; + linePtr2 = linePtr2->nextPtr) { + if (linePtr2 == NULL) { + panic("TkBTreePixels couldn't find line"); + } + index += linePtr2->pixelHeight; + } + + /* + * Now work up through the levels of the tree one at a time, + * counting how many lines are in nodes preceding the current + * node. + */ + + for (parentPtr = nodePtr->parentPtr ; parentPtr != NULL; + nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) { + for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr; + nodePtr2 = nodePtr2->nextPtr) { + if (nodePtr2 == NULL) { + panic("TkBTreePixels couldn't find node"); + } + index += nodePtr2->numPixels; + } + } + return index; +} + +/* + *---------------------------------------------------------------------- + * * TkBTreeLineIndex -- * * Given a pointer to a line in a B-tree, return the numerical @@ -1137,7 +1375,9 @@ TkBTreeUnlinkSegment(tree, segPtr, linePtr) * a B-tree of text. * * Results: - * None. + * 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 @@ -1150,7 +1390,7 @@ TkBTreeUnlinkSegment(tree, segPtr, linePtr) *---------------------------------------------------------------------- */ -void +int TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) register TkTextIndex *index1Ptr; /* Indicates first character in * range. */ @@ -1166,7 +1406,8 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) TkTextLine *cleanupLinePtr; int oldState; int changed; - + int 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 @@ -1188,6 +1429,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) segPtr->size = 0; segPtr->body.toggle.tagPtr = tagPtr; segPtr->body.toggle.inNodeCounts = 0; + anyChanges = 1; } /* @@ -1199,6 +1441,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search); cleanupLinePtr = index1Ptr->linePtr; while (TkBTreeNextTag(&search)) { + anyChanges = 1; oldState ^= 1; segPtr = search.segPtr; prevPtr = search.curIndex.linePtr->segPtr; @@ -1257,6 +1500,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) segPtr->size = 0; segPtr->body.toggle.tagPtr = tagPtr; segPtr->body.toggle.inNodeCounts = 0; + anyChanges = 1; } /* @@ -1264,14 +1508,17 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) * these are different. */ - CleanupLine(cleanupLinePtr); - if (cleanupLinePtr != index2Ptr->linePtr) { - CleanupLine(index2Ptr->linePtr); + if (anyChanges) { + CleanupLine(cleanupLinePtr); + if (cleanupLinePtr != index2Ptr->linePtr) { + CleanupLine(index2Ptr->linePtr); + } } if (tkBTreeDebug) { TkBTreeCheck(index1Ptr->tree); } + return anyChanges; } /* @@ -1789,7 +2036,8 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr) searchPtr->curIndex = index0; index1Ptr = &index0; } else { - TkTextIndexBackChars(index1Ptr, 1, &searchPtr->curIndex); + TkTextIndexBackChars(index1Ptr, 1, &searchPtr->curIndex, + COUNT_INDICES); } searchPtr->segPtr = NULL; searchPtr->nextPtr = TkTextIndexToSeg(&searchPtr->curIndex, &offset); @@ -1805,7 +2053,7 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr) backOne = *index2Ptr; searchPtr->lastPtr = NULL; /* Signals special case for 1.0 */ } else { - TkTextIndexBackChars(index2Ptr, 1, &backOne); + TkTextIndexBackChars(index2Ptr, 1, &backOne, COUNT_INDICES); searchPtr->lastPtr = TkTextIndexToSeg(&backOne, (int *) NULL); } searchPtr->tagPtr = tagPtr; @@ -2451,7 +2699,7 @@ TkBTreeGetTags(indexPtr, numTagsPtr) /* ARGSUSED */ int TkTextIsElided(textPtr, indexPtr) - TkText *textPtr; /* Overall information about text widget. */ + CONST TkText *textPtr; /* Overall information about text widget. */ CONST TkTextIndex *indexPtr;/* The character in the text for which * display information is wanted. */ { @@ -2469,7 +2717,7 @@ TkTextIsElided(textPtr, indexPtr) register TkTextTag *tagPtr = NULL; register int i, index; - /* almost always avoid malloc, so stay out of system calls */ + /* Almost always avoid malloc, so stay out of system calls */ if (LOTSA_TAGS < numTags) { tagCnts = (int *)ckalloc((unsigned)sizeof(int) * numTags); tagPtrs = (TkTextTag **)ckalloc((unsigned)sizeof(TkTextTag *) * numTags); @@ -2806,7 +3054,7 @@ CheckNodeConsistency(nodePtr) register Summary *summaryPtr, *summaryPtr2; register TkTextLine *linePtr; register TkTextSegment *segPtr; - int numChildren, numLines, toggleCount, minChildren; + int numChildren, numLines, numPixels, toggleCount, minChildren; if (nodePtr->parentPtr != NULL) { minChildren = MIN_CHILDREN; @@ -2823,6 +3071,7 @@ CheckNodeConsistency(nodePtr) numChildren = 0; numLines = 0; + numPixels = 0; if (nodePtr->level == 0) { for (linePtr = nodePtr->children.linePtr; linePtr != NULL; linePtr = linePtr->nextPtr) { @@ -2850,6 +3099,7 @@ CheckNodeConsistency(nodePtr) } numChildren++; numLines++; + numPixels += linePtr->pixelHeight; } } else { for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL; @@ -2881,6 +3131,7 @@ CheckNodeConsistency(nodePtr) } numChildren++; numLines += childNodePtr->numLines; + numPixels += childNodePtr->numPixels; } } if (numChildren != nodePtr->numChildren) { @@ -2891,6 +3142,10 @@ CheckNodeConsistency(nodePtr) panic("CheckNodeConsistency: mismatch in numLines (%d %d)", numLines, nodePtr->numLines); } + if (numPixels != nodePtr->numPixels) { + panic("CheckNodeConsistency: mismatch in numPixels (%d %d)", + numPixels, nodePtr->numPixels); + } for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { @@ -2997,6 +3252,7 @@ Rebalance(treePtr, nodePtr) newPtr->children.nodePtr = nodePtr; newPtr->numChildren = 1; newPtr->numLines = nodePtr->numLines; + newPtr->numPixels = nodePtr->numPixels; RecomputeNodeCounts(newPtr); treePtr->rootPtr = newPtr; } @@ -3209,6 +3465,7 @@ RecomputeNodeCounts(nodePtr) } nodePtr->numChildren = 0; nodePtr->numLines = 0; + nodePtr->numPixels = 0; /* * Scan through the children, adding the childrens' tag counts into @@ -3221,6 +3478,7 @@ RecomputeNodeCounts(nodePtr) linePtr = linePtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines++; + nodePtr->numPixels += linePtr->pixelHeight; linePtr->parentPtr = nodePtr; for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { @@ -3252,6 +3510,7 @@ RecomputeNodeCounts(nodePtr) childPtr = childPtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines += childPtr->numLines; + nodePtr->numPixels += childPtr->numPixels; childPtr->parentPtr = nodePtr; for (summaryPtr2 = childPtr->summaryPtr; summaryPtr2 != NULL; summaryPtr2 = summaryPtr2->nextPtr) { @@ -3343,6 +3602,34 @@ TkBTreeNumLines(tree) } /* + *---------------------------------------------------------------------- + * + * TkBTreeNumPixels -- + * + * This procedure returns a count of the number of pixels of + * text present in a given B-tree. + * + * 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 tree is + * maintained with zero height, it doesn't feature in this + * calculation). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TkBTreeNumPixels(tree) + TkTextBTree tree; /* Information about tree. */ +{ + BTree *treePtr = (BTree *) tree; + return treePtr->rootPtr->numPixels; +} + +/* *-------------------------------------------------------------- * * CharSplitProc -- diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index 48f2963..701fe96 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -3,7 +3,9 @@ * * This module provides facilities to display text widgets. It is * the only place where information is kept about the screen layout - * of text widgets. + * of text widgets. (Well, strictly, each TkTextLine caches its + * last observed pixel height, but that information is + * calculated here). * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. @@ -11,7 +13,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextDisp.c,v 1.17 2003/07/07 20:39:50 hobbs Exp $ + * RCS: @(#) $Id: tkTextDisp.c,v 1.18 2003/10/31 09:02:10 vincentdarley Exp $ */ #include "tkPort.h" @@ -119,11 +121,15 @@ typedef struct DLine { int y; /* Y-position at which line is supposed to * be drawn (topmost pixel of rectangular * area occupied by line). */ - int oldY; /* Y-position at which line currently - * appears on display. -1 means line isn't - * currently visible on display and must be - * redrawn. This is used to move lines by - * scrolling rather than re-drawing. */ + int oldY; /* Y-position at which line + * currently appears on display. This + * is used to move lines by scrolling + * rather than re-drawing. If 'flags' + * have the OLD_Y_INVALID bit set, then + * we will never examine this field + * (which means line isn't currently + * visible on display and must be + * redrawn). */ int height; /* Height of line, in pixels. */ int baseline; /* Offset of text baseline from y, in * pixels. */ @@ -171,14 +177,20 @@ typedef struct DLine { * BOTTOM_LINE - Non-zero means that this was the bottom line * in the window the last time that the window * was laid out. - * IS_DISABLED - This Dline cannot be edited. + * OLD_Y_INVALID - The value of oldY in the structure is + * not valid or useful and should not be + * examined. 'oldY' is only useful when the + * DLine is currently displayed at a + * different position and we wish to + * re-display it via scrolling, so this + * means the DLine needs redrawing. */ #define HAS_3D_BORDER 1 #define NEW_LAYOUT 2 #define TOP_LINE 4 #define BOTTOM_LINE 8 -#define IS_DISABLED 16 +#define OLD_Y_INVALID 16 /* * Overall display information for a text widget: @@ -189,6 +201,10 @@ typedef struct TextDInfo { * to TextStyles for this widget. */ DLine *dLinePtr; /* First in list of all display lines for * this widget, in order from top to bottom. */ + int topPixelOffset; /* Identifies first pixel in top display + * line to display in window. */ + int newTopPixelOffset; /* Desired first pixel in top display + * line to display in window. */ GC copyGC; /* Graphics context for copying from off- * screen pixmaps onto screen. */ GC scrollGC; /* Graphics context for copying from one place @@ -216,11 +232,11 @@ typedef struct TextDInfo { * Information used for scrolling: */ - int newByteOffset; /* Desired x scroll position, measured as the + int newXByteOffset; /* Desired x scroll position, measured as the * number of average-size characters off-screen * to the left for a line with no left * margin. */ - int curPixelOffset; /* Actual x scroll position, measured as the + int curXPixelOffset; /* Actual x scroll position, measured as the * number of pixels off-screen to the left. */ int maxLength; /* Length in pixels of longest line that's * visible in window (length may exceed window @@ -243,7 +259,7 @@ typedef struct TextDInfo { * left edge of the window when the scan * started. */ int scanMarkX; /* X-position of mouse at time scan started. */ - int scanTotalScroll; /* Total scrolling (in screen lines) that has + int scanTotalYScroll; /* Total scrolling (in screen pixels) that has * occurred since scanMarkY was set. */ int scanMarkY; /* Y-position of mouse at time scan started. */ @@ -264,6 +280,28 @@ typedef struct TextDInfo { * could dump core. */ int flags; /* Various flag values: see below for * definitions. */ + /* + * Information used to handle the asynchronous updating of the + * y-scrollbar and the vertical height calculations: + */ + + int lineMetricUpdateEpoch; /* Stores a number which is incremented + * each time the text widget changes in + * a significant way (e.g. resizing or + * geometry-influencing tag changes). */ + int currentMetricUpdateLine;/* Stores a counter which is used to + * iterate over the logical lines + * contained in the widget and update + * their geometry calculations, if they + * are out of date. */ + int lastMetricUpdateLine; /* When the current update line reaches + * this line, we are done and should + * stop the asychronous callback + * mechanism. */ + Tcl_TimerToken lineUpdateTimer; /* A token pointing to the current + * line metric update callback. */ + Tcl_TimerToken scrollbarTimer; /* A token pointing to the current + * scrollbar update callback. */ } TextDInfo; /* @@ -302,6 +340,20 @@ typedef struct CharInfo { #define REPICK_NEEDED 8 /* + * Action values for FreeDLines: + * + * DLINE_FREE: Free the lines, but no need to unlink them + * from the current list of actual display lines. + * DLINE_UNLINK: Free and unlink from current display. + * DLINE_FREE_TEMP: Free, but don't unlink, and also don't + * set 'dLinesInvalidated'. + */ + +#define DLINE_FREE 0 +#define DLINE_UNLINK 1 +#define DLINE_FREE_TEMP 2 + +/* * The following counters keep statistics about redisplay that can be * checked to see how clever this code is at reducing redisplays. */ @@ -352,33 +404,58 @@ static void DisplayText _ANSI_ARGS_((ClientData clientData)); static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, CONST TkTextIndex *indexPtr)); static void FreeDLines _ANSI_ARGS_((TkText *textPtr, - DLine *firstPtr, DLine *lastPtr, int unlink)); + DLine *firstPtr, DLine *lastPtr, int action)); static void FreeStyle _ANSI_ARGS_((TkText *textPtr, TextStyle *stylePtr)); static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr)); + CONST TkTextIndex *indexPtr)); static void GetXView _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, int report)); static void GetYView _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, int report)); +static int GetPixelCount _ANSI_ARGS_((TkText *textPtr, + DLine *dlPtr)); static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr)); + CONST TkTextIndex *indexPtr)); static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont, CONST char *source, int maxBytes, int startX, int maxX, int tabOrigin, int *nextXPtr)); static void MeasureUp _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *srcPtr, int distance, - TkTextIndex *dstPtr)); + CONST TkTextIndex *srcPtr, int distance, + TkTextIndex *dstPtr, int *overlap)); static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x, int tabOrigin)); static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); -static void ScrollByLines _ANSI_ARGS_((TkText *textPtr, +static void YScrollByLines _ANSI_ARGS_((TkText *textPtr, + int offset)); +static void YScrollByPixels _ANSI_ARGS_((TkText *textPtr, int offset)); static int SizeOfTab _ANSI_ARGS_((TkText *textPtr, TkTextTabArray *tabArrayPtr, int index, int x, int maxX)); static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr, TkRegion region)); +static int TextCalculateDisplayLineHeight _ANSI_ARGS_(( + TkText *textPtr, CONST TkTextIndex *indexPtr, + int *byteCountPtr)); +static void DlineIndexOfX _ANSI_ARGS_((TkText *textPtr, + DLine *dlPtr, int x, TkTextIndex *indexPtr)); +static int DlineXOfIndex _ANSI_ARGS_((TkText *textPtr, + DLine *dlPtr, int byteIndex)); +static int TextGetScrollInfoObj _ANSI_ARGS_((Tcl_Interp *interp, + TkText *textPtr, int objc, + Tcl_Obj *CONST objv[], double *dblPtr, + int *intPtr)); + +/* + * Result values returned by TextGetScrollInfo: + */ + +#define TKTEXT_SCROLL_MOVETO 1 +#define TKTEXT_SCROLL_PAGES 2 +#define TKTEXT_SCROLL_UNITS 3 +#define TKTEXT_SCROLL_ERROR 4 +#define TKTEXT_SCROLL_PIXELS 5 /* @@ -412,10 +489,10 @@ TkTextCreateDInfo(textPtr) dInfoPtr->copyGC = None; gcValues.graphics_exposures = True; dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, - &gcValues); + &gcValues); dInfoPtr->topOfEof = 0; - dInfoPtr->newByteOffset = 0; - dInfoPtr->curPixelOffset = 0; + dInfoPtr->newXByteOffset = 0; + dInfoPtr->curXPixelOffset = 0; dInfoPtr->maxLength = 0; dInfoPtr->xScrollFirst = -1; dInfoPtr->xScrollLast = -1; @@ -423,10 +500,25 @@ TkTextCreateDInfo(textPtr) dInfoPtr->yScrollLast = -1; dInfoPtr->scanMarkIndex = 0; dInfoPtr->scanMarkX = 0; - dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanTotalYScroll = 0; dInfoPtr->scanMarkY = 0; dInfoPtr->dLinesInvalidated = 0; dInfoPtr->flags = DINFO_OUT_OF_DATE; + dInfoPtr->topPixelOffset = 0; + dInfoPtr->newTopPixelOffset = 0; + dInfoPtr->currentMetricUpdateLine = -1; + dInfoPtr->lastMetricUpdateLine = -1; + dInfoPtr->lineMetricUpdateEpoch = 1; + + /* Add a refCount for each of the idle call-backs */ + textPtr->refCount++; + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(0, + TkTextAsyncUpdateLineMetrics, (ClientData) textPtr); + textPtr->refCount++; + dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200, + TkTextUpdateYScrollbar, (ClientData) textPtr); + + textPtr->dInfoPtr = dInfoPtr; } @@ -460,7 +552,7 @@ TkTextFreeDInfo(textPtr) * all free then styleTable will be empty. */ - FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); + FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, DLINE_UNLINK); Tcl_DeleteHashTable(&dInfoPtr->styleTable); if (dInfoPtr->copyGC != None) { Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); @@ -469,6 +561,16 @@ TkTextFreeDInfo(textPtr) if (dInfoPtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr); } + if (dInfoPtr->lineUpdateTimer != NULL) { + Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer); + textPtr->refCount--; + dInfoPtr->lineUpdateTimer = NULL; + } + if (dInfoPtr->scrollbarTimer != NULL) { + Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer); + textPtr->refCount--; + dInfoPtr->scrollbarTimer = NULL; + } ckfree((char *) dInfoPtr); } @@ -493,7 +595,7 @@ TkTextFreeDInfo(textPtr) static TextStyle * GetStyle(textPtr, indexPtr) TkText *textPtr; /* Overall information about text widget. */ - TkTextIndex *indexPtr; /* The character in the text for which + CONST TkTextIndex *indexPtr;/* The character in the text for which * display information is wanted. */ { TkTextTag **tagPtrs; @@ -762,6 +864,22 @@ FreeStyle(textPtr, stylePtr) * * Side effects: * Storage is allocated for the new DLine. + * + * See the comments in 'GetYView' for some thoughts on what the + * side-effects of this call (or its callers) should be -- the + * synchronisation of TkTextLine->pixelHeight with the sum of + * the results of this function operating on all display lines + * within each logical line. Ideally the code should be + * refactored to ensure the cached pixel height is never behind + * what is known when this function is called elsewhere. + * + * Unfortunately, this function is currently called from many + * different places, not just to layout a display line for actual + * display, but also simply to calculate some metric or other of one + * or more display lines (typically the height). It would be a good + * idea to do some profiling of typical text widget usage and the + * way in which this is called and see if some optimization could or + * should be done. * *---------------------------------------------------------------------- */ @@ -769,7 +887,7 @@ FreeStyle(textPtr, stylePtr) static DLine * LayoutDLine(textPtr, indexPtr) TkText *textPtr; /* Overall information about text widget. */ - TkTextIndex *indexPtr; /* Beginning of display line. May not + CONST TkTextIndex *indexPtr;/* Beginning of display line. May not * necessarily point to a character segment. */ { register DLine *dlPtr; /* New display line. */ @@ -825,12 +943,12 @@ LayoutDLine(textPtr, indexPtr) dlPtr->index = *indexPtr; dlPtr->byteCount = 0; dlPtr->y = 0; - dlPtr->oldY = -1; + dlPtr->oldY = 0; /* Only setting this to avoid compiler warnings */ dlPtr->height = 0; dlPtr->baseline = 0; dlPtr->chunkPtr = NULL; dlPtr->nextPtr = NULL; - dlPtr->flags = NEW_LAYOUT; + dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID; /* * Special case entirely elide line as there may be 1000s or more @@ -900,17 +1018,26 @@ LayoutDLine(textPtr, indexPtr) while (segPtr != NULL) { /* - * Every line still gets at least one chunk due to expectations - * in the rest of the code, but we are able to skip elided portions - * of the line quickly. - * If current chunk is elided and last chunk was too, coalese + * Every logical line still gets at least one chunk due to + * expectations in the rest of the code, but we are able to skip + * elided portions of the line quickly. + * + * If current chunk is elided and last chunk was too, coalese. + * + * This also means that each logical line which is entirely + * elided still gets laid out into a DLine, but with zero height. + * This isn't particularly a problem, but it does seem somewhat + * unnecessary. If/when we fix [Tk Bug 443848] (see below) + * then we will probably have to remove such zero height DLines + * too. */ if (elide && (lastChunkPtr != NULL) && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) { if ((elidesize = segPtr->size - byteOffset) > 0) { curIndex.byteIndex += elidesize; lastChunkPtr->numBytes += elidesize; - breakByteOffset = lastChunkPtr->breakIndex = lastChunkPtr->numBytes; + breakByteOffset = lastChunkPtr->breakIndex + = lastChunkPtr->numBytes; /* * If have we have a tag toggle, there is a chance * that invisibility state changed, so bail out @@ -990,22 +1117,27 @@ LayoutDLine(textPtr, indexPtr) } chunkPtr->x = x; if (elide && maxBytes) { - /* don't free style here, as other code expects to be able to do that */ - /*breakByteOffset =*/ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes; + /* + * Don't free style here, as other code expects to be able to + * do that. + */ + /* breakByteOffset =*/ + chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes; chunkPtr->width = 0; chunkPtr->minAscent = chunkPtr->minDescent = chunkPtr->minHeight = 0; - /* would just like to point to canonical empty chunk */ + /* Would just like to point to canonical empty chunk */ chunkPtr->displayProc = (Tk_ChunkDisplayProc *) NULL; chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) NULL; chunkPtr->measureProc = ElideMeasureProc; chunkPtr->bboxProc = ElideBboxProc; code = 1; - } else - code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr, + } else { + code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr, byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode, chunkPtr); + } if (code <= 0) { FreeStyle(textPtr, chunkPtr->stylePtr); if (code < 0) { @@ -1029,7 +1161,7 @@ LayoutDLine(textPtr, indexPtr) } break; } - if (chunkPtr->numBytes > 0) { + if (!elide && chunkPtr->numBytes > 0) { noCharsYet = 0; lastCharChunkPtr = chunkPtr; } @@ -1073,6 +1205,16 @@ LayoutDLine(textPtr, indexPtr) if (byteOffset >= segPtr->size) { byteOffset = 0; segPtr = segPtr->nextPtr; + if (elide && segPtr == NULL) { + /* + * An elided section started on this line, and carries on + * until the newline. Currently this forces a new line + * anyway (i.e. even though the newline is elided it + * still takes effect). This is because the code + * currently doesn't allow two or more logical lines to + * appear on the same display line. [Tk Bug #443848] + */ + } } chunkPtr = NULL; @@ -1240,7 +1382,7 @@ UpdateDisplayInfo(textPtr) register DLine *dlPtr, *prevPtr; TkTextIndex index; TkTextLine *lastLinePtr; - int y, maxY, pixelOffset, maxOffset; + int y, maxY, xPixelOffset, maxOffset, lineHeight; if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) { return; @@ -1254,7 +1396,12 @@ UpdateDisplayInfo(textPtr) index = textPtr->topIndex; dlPtr = FindDLine(dInfoPtr->dLinePtr, &index); if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { - FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1); + FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK); + } + if (index.byteIndex == 0) { + lineHeight = 0; + } else { + lineHeight = -1; } /* @@ -1265,10 +1412,10 @@ UpdateDisplayInfo(textPtr) */ lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); + TkBTreeNumLines(textPtr->tree)); dlPtr = dInfoPtr->dLinePtr; prevPtr = NULL; - y = dInfoPtr->y; + y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; maxY = dInfoPtr->maxY; while (1) { register DLine *newPtr; @@ -1300,6 +1447,7 @@ UpdateDisplayInfo(textPtr) */ if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) { + /* * Case (b) -- must make new DLine. */ @@ -1325,7 +1473,7 @@ UpdateDisplayInfo(textPtr) } else { prevPtr->nextPtr = newPtr; if (prevPtr->flags & HAS_3D_BORDER) { - prevPtr->oldY = -1; + prevPtr->flags |= OLD_Y_INVALID; } } newPtr->nextPtr = dlPtr; @@ -1343,7 +1491,7 @@ UpdateDisplayInfo(textPtr) if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL) && (prevPtr->flags & (NEW_LAYOUT))) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } goto lineOK; } @@ -1357,7 +1505,7 @@ UpdateDisplayInfo(textPtr) */ newPtr = dlPtr->nextPtr; - FreeDLines(textPtr, dlPtr, newPtr, 0); + FreeDLines(textPtr, dlPtr, newPtr, DLINE_FREE); dlPtr = newPtr; if (prevPtr != NULL) { prevPtr->nextPtr = newPtr; @@ -1374,6 +1522,9 @@ UpdateDisplayInfo(textPtr) lineOK: dlPtr->y = y; y += dlPtr->height; + if (lineHeight != -1) { + lineHeight += dlPtr->height; + } TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); prevPtr = dlPtr; dlPtr = dlPtr->nextPtr; @@ -1392,10 +1543,36 @@ UpdateDisplayInfo(textPtr) nextPtr = nextPtr->nextPtr; } if (nextPtr != dlPtr) { - FreeDLines(textPtr, dlPtr, nextPtr, 0); + FreeDLines(textPtr, dlPtr, nextPtr, DLINE_FREE); prevPtr->nextPtr = nextPtr; dlPtr = nextPtr; } + + if ((lineHeight != -1) + && (lineHeight > prevPtr->index.linePtr->pixelHeight)) { + /* + * The logical line height we just calculated is actually + * larger than the currently cached height of the + * text line. That is fine (the text line heights + * are only calculated asynchronously), but we must + * update the cached height so that any counts made + * with DLine pointers do not exceed counts made + * through the BTree. + */ + TkBTreeAdjustPixelHeight(prevPtr->index.linePtr, + lineHeight); + /* + * I believe we can be 100% sure that we started at the + * beginning of the logical line, so we can also adjust + * the 'pixelCalculationEpoch' to mark it as being up to + * date. There is a slight concern that we might not + * have got this right for the first line in the + * re-display. + */ + prevPtr->index.linePtr->pixelCalculationEpoch = + dInfoPtr->lineMetricUpdateEpoch; + } + lineHeight = 0; } /* @@ -1414,75 +1591,135 @@ UpdateDisplayInfo(textPtr) * Delete any DLine structures that don't fit on the screen. */ - FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1); + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_UNLINK); /* *-------------------------------------------------------------- * If there is extra space at the bottom of the window (because * we've hit the end of the text), then bring in more lines at * the top of the window, if there are any, to fill in the view. + * + * Since the top line may only be partially visible, we try first + * to simply show more pixels from that line (newTopPixelOffset). + * If that isn't enough, we have to layout more lines. *-------------------------------------------------------------- */ if (y < maxY) { - int lineNum, spaceLeft, bytesToCount; - DLine *lowestPtr; - - /* - * Layout an entire text line (potentially > 1 display line), - * then link in as many display lines as fit without moving - * the bottom line out of the window. Repeat this until - * all the extra space has been used up or we've reached the - * beginning of the text. - */ - - spaceLeft = maxY - y; - lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); - bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; - if (bytesToCount == 0) { - bytesToCount = INT_MAX; - lineNum--; - } - for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { - index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); - index.byteIndex = 0; - lowestPtr = NULL; - - do { - dlPtr = LayoutDLine(textPtr, &index); - dlPtr->nextPtr = lowestPtr; - lowestPtr = dlPtr; - if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; } /* elide */ - TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); - bytesToCount -= dlPtr->byteCount; - } while ((bytesToCount > 0) - && (index.linePtr == lowestPtr->index.linePtr)); + int spaceLeft = maxY - y; + + if (spaceLeft <= dInfoPtr->newTopPixelOffset) { + /* + * We can full up all the needed space just by showing + * more of the current top line + */ + dInfoPtr->newTopPixelOffset -= spaceLeft; + y += spaceLeft; + spaceLeft = 0; + } else { + int lineNum, bytesToCount; + DLine *lowestPtr; + + /* + * Add in all of the current top line, which won't + * be enough to bring y up to maxY (if it was we + * would be in the 'if' block above). + */ + y += dInfoPtr->newTopPixelOffset; + dInfoPtr->newTopPixelOffset = 0; /* - * Scan through the display lines from the bottom one up to - * the top one. + * Layout an entire text line (potentially > 1 display line), + * then link in as many display lines as fit without moving + * the bottom line out of the window. Repeat this until + * all the extra space has been used up or we've reached the + * beginning of the text. */ - while (lowestPtr != NULL) { - dlPtr = lowestPtr; - spaceLeft -= dlPtr->height; - if (spaceLeft < 0) { - break; + spaceLeft = maxY - y; + lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); + bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; + if (bytesToCount == 0) { + bytesToCount = INT_MAX; + lineNum--; + } + for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { + int pixelHeight = 0; + + index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + index.byteIndex = 0; + lowestPtr = NULL; + + do { + dlPtr = LayoutDLine(textPtr, &index); + pixelHeight += dlPtr->height; + dlPtr->nextPtr = lowestPtr; + lowestPtr = dlPtr; + if (dlPtr->length == 0 && dlPtr->height == 0) { + bytesToCount--; break; + } /* elide */ + TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); + bytesToCount -= dlPtr->byteCount; + } while ((bytesToCount > 0) + && (index.linePtr == lowestPtr->index.linePtr)); + + /* + * We may not have examined the entire line (depending + * on the value of 'bytesToCount', so we only want + * to set this if it is genuinely bigger). + */ + if (pixelHeight > lowestPtr->index.linePtr->pixelHeight) { + TkBTreeAdjustPixelHeight(lowestPtr->index.linePtr, + pixelHeight); + if (index.linePtr != lowestPtr->index.linePtr) { + /* + * We examined the entire line, so can update + * the epoch. + */ + lowestPtr->index.linePtr->pixelCalculationEpoch = + dInfoPtr->lineMetricUpdateEpoch; + } } - lowestPtr = dlPtr->nextPtr; - dlPtr->nextPtr = dInfoPtr->dLinePtr; - dInfoPtr->dLinePtr = dlPtr; - if (tkTextDebug) { - char string[TK_POS_CHARS]; - TkTextPrintIndex(&dlPtr->index, string); - Tcl_SetVar2(textPtr->interp, "tk_textRelayout", + /* + * Scan through the display lines from the bottom one up to + * the top one. + */ + + while (lowestPtr != NULL) { + dlPtr = lowestPtr; + spaceLeft -= dlPtr->height; + lowestPtr = dlPtr->nextPtr; + dlPtr->nextPtr = dInfoPtr->dLinePtr; + dInfoPtr->dLinePtr = dlPtr; + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + TkTextPrintIndex(&dlPtr->index, string); + Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, string, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); + } + if (spaceLeft <= 0) { + break; + } + } + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE); + bytesToCount = INT_MAX; + } + /* + * We've filled in the space we wanted to, and we + * need to store any extra overlap we've just + * created for the top line. + */ + if (lineNum >= 0) { + dInfoPtr->newTopPixelOffset = -spaceLeft; + if (spaceLeft > 0 + || dInfoPtr->newTopPixelOffset >= dInfoPtr->dLinePtr->height) { + /* Bad situation */ + panic("Pixel height problem while laying out text widget"); } } - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); - bytesToCount = INT_MAX; } /* @@ -1492,7 +1729,7 @@ UpdateDisplayInfo(textPtr) */ textPtr->topIndex = dInfoPtr->dLinePtr->index; - y = dInfoPtr->y; + y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { if (y > dInfoPtr->maxY) { @@ -1517,21 +1754,30 @@ UpdateDisplayInfo(textPtr) dlPtr = dInfoPtr->dLinePtr; if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } while (1) { if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) && (dlPtr->flags & HAS_3D_BORDER)) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; + } + /* + * If the old top-line was not completely showing (i.e. the + * pixelOffset is non-zero) and is no longer the top-line, then + * we must re-draw it. + */ + if ((dlPtr->flags & TOP_LINE) + && (dInfoPtr->topPixelOffset != 0) && (dlPtr != dInfoPtr->dLinePtr)) { + dlPtr->flags |= OLD_Y_INVALID; } if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL) && (dlPtr->flags & HAS_3D_BORDER)) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } if (dlPtr->nextPtr == NULL) { if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & BOTTOM_LINE)) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } dlPtr->flags &= ~TOP_LINE; dlPtr->flags |= BOTTOM_LINE; @@ -1541,6 +1787,7 @@ UpdateDisplayInfo(textPtr) dlPtr = dlPtr->nextPtr; } dInfoPtr->dLinePtr->flags |= TOP_LINE; + dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset; /* * Arrange for scrollbars to be updated. @@ -1569,18 +1816,18 @@ UpdateDisplayInfo(textPtr) } maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x) + textPtr->charWidth - 1)/textPtr->charWidth; - if (dInfoPtr->newByteOffset > maxOffset) { - dInfoPtr->newByteOffset = maxOffset; + if (dInfoPtr->newXByteOffset > maxOffset) { + dInfoPtr->newXByteOffset = maxOffset; } - if (dInfoPtr->newByteOffset < 0) { - dInfoPtr->newByteOffset = 0; + if (dInfoPtr->newXByteOffset < 0) { + dInfoPtr->newXByteOffset = 0; } - pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth; - if (pixelOffset != dInfoPtr->curPixelOffset) { - dInfoPtr->curPixelOffset = pixelOffset; + xPixelOffset = dInfoPtr->newXByteOffset * textPtr->charWidth; + if (xPixelOffset != dInfoPtr->curXPixelOffset) { + dInfoPtr->curXPixelOffset = xPixelOffset; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } } } @@ -1603,23 +1850,29 @@ UpdateDisplayInfo(textPtr) */ static void -FreeDLines(textPtr, firstPtr, lastPtr, unlink) - TkText *textPtr; /* Information about overall text - * widget. */ - register DLine *firstPtr; /* Pointer to first DLine to free up. */ - DLine *lastPtr; /* Pointer to DLine just after last - * one to free (NULL means everything - * starting with firstPtr). */ - int unlink; /* 1 means DLines are currently linked - * into the list rooted at - * textPtr->dInfoPtr->dLinePtr and - * they have to be unlinked. 0 means - * just free without unlinking. */ +FreeDLines(textPtr, firstPtr, lastPtr, action) + TkText *textPtr; /* Information about overall text + * widget. */ + register DLine *firstPtr; /* Pointer to first DLine to free up. */ + DLine *lastPtr; /* Pointer to DLine just after last + * one to free (NULL means everything + * starting with firstPtr). */ + int action; /* DLINE_UNLINK means DLines are + * currently linked into the list + * rooted at + * textPtr->dInfoPtr->dLinePtr and + * they have to be unlinked. + * DLINE_FREE means just free without + * unlinking. DLINE_FREE_TEMP means + * the DLine given is just a + * temporary one and we shouldn't + * invalidate anything for the + * overall widget. */ { register TkTextDispChunk *chunkPtr, *nextChunkPtr; register DLine *nextDLinePtr; - if (unlink) { + if (action == DLINE_UNLINK) { if (textPtr->dInfoPtr->dLinePtr == firstPtr) { textPtr->dInfoPtr->dLinePtr = lastPtr; } else { @@ -1645,7 +1898,9 @@ FreeDLines(textPtr, firstPtr, lastPtr, unlink) ckfree((char *) firstPtr); firstPtr = nextDLinePtr; } - textPtr->dInfoPtr->dLinesInvalidated = 1; + if (action != DLINE_FREE_TEMP) { + textPtr->dInfoPtr->dLinesInvalidated = 1; + } } /* @@ -1680,7 +1935,7 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) register TkTextDispChunk *chunkPtr; TextDInfo *dInfoPtr = textPtr->dInfoPtr; Display *display; - int height, x; + int height, y_off; if (dlPtr->chunkPtr == NULL) return; @@ -1710,8 +1965,8 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) if (textPtr->state == TK_TEXT_STATE_NORMAL) { for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); chunkPtr = chunkPtr->nextPtr) { - x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; if (chunkPtr->displayProc == TkTextInsertDisplayProc) { + int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, @@ -1725,7 +1980,6 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) * foreground information. Note: we have to call the displayProc * even for chunks that are off-screen. This is needed, for * example, so that embedded windows can be unmapped in this case. - * Conve */ for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); @@ -1738,54 +1992,59 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) continue; } - x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; - if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { - /* - * Note: we have to call the displayProc even for chunks - * that are off-screen. This is needed, for example, so - * that embedded windows can be unmapped in this case. - * Display the chunk at a coordinate that can be clearly - * identified by the displayProc as being off-screen to - * the left (the displayProc may not be able to tell if - * something is off to the right). - */ + /* + * Don't call if elide. This tax ok since not very many + * visible DLine's in an area, but potentially many elide + * ones + */ + if (chunkPtr->displayProc != NULL) { + int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; - if (chunkPtr->displayProc != NULL) - (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width, - dlPtr->spaceAbove, - dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, - dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, - dlPtr->y + dlPtr->spaceAbove); - } else { - /* don't call if elide. This tax ok since not very many visible DLine's in - an area, but potentially many elide ones */ - if (chunkPtr->displayProc != NULL) + if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { + /* + * Note: we have to call the displayProc even for chunks + * that are off-screen. This is needed, for example, so + * that embedded windows can be unmapped in this case. + * Display the chunk at a coordinate that can be clearly + * identified by the displayProc as being off-screen to + * the left (the displayProc may not be able to tell if + * something is off to the right). + */ + x = -chunkPtr->width; + } (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, dlPtr->y + dlPtr->spaceAbove); } + if (dInfoPtr->dLinesInvalidated) { return; } } /* - * Copy the pixmap onto the screen. If this is the last line on - * the screen then copy a piece of the line, so that it doesn't - * overflow into the border area. Another special trick: copy the - * padding area to the left of the line; this is because the - * insertion cursor sometimes overflows onto that area and we want - * to get as much of the cursor as possible. + * Copy the pixmap onto the screen. If this is the first or last + * line on the screen then copy a piece of the line, so that it + * doesn't overflow into the border area. Another special trick: + * copy the padding area to the left of the line; this is because the + * insertion cursor sometimes overflows onto that area and we want to + * get as much of the cursor as possible. */ height = dlPtr->height; if ((height + dlPtr->y) > dInfoPtr->maxY) { height = dInfoPtr->maxY - dlPtr->y; } + if (dlPtr->y < dInfoPtr->y) { + y_off = dInfoPtr->y - dlPtr->y; + height -= y_off; + } else { + y_off = 0; + } XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC, - dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), - (unsigned) height, dInfoPtr->x, dlPtr->y); + dInfoPtr->x, y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), + (unsigned) height, dInfoPtr->x, dlPtr->y + y_off); linesRedrawn++; } @@ -1857,7 +2116,7 @@ DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap) */ display = Tk_Display(textPtr->tkwin); - minX = dInfoPtr->curPixelOffset; + minX = dInfoPtr->curXPixelOffset; xOffset = dInfoPtr->x - minX; maxX = minX + dInfoPtr->maxX - dInfoPtr->x; chunkPtr = dlPtr->chunkPtr; @@ -2158,6 +2417,692 @@ DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap) /* *---------------------------------------------------------------------- * + * TkTextAsyncUpdateLineMetrics -- + * + * This procedure is invoked as a background handler to update the + * pixel-height calculations of individual lines in an + * asychronous manner. + * + * Currently a timer-handler is used for this purpose, which + * continuously reschedules itself. It may well be better to + * use some other approach (e.g. a background thread). We can't + * use an idle-callback because of a known bug in Tcl/Tk in + * which idle callbacks are not allowed to re-schedule + * themselves. This just causes an effective infinite loop. + * + * Results: + * None. + * + * Side effects: + * Line heights may be recalculated. + * + *---------------------------------------------------------------------- + */ + +void +TkTextAsyncUpdateLineMetrics(clientData) + ClientData clientData; /* Information about widget. */ +{ + register TkText *textPtr = (TkText *) clientData; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + int lineNum; + + dInfoPtr->lineUpdateTimer = NULL; + + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + + /* + * The widget has been deleted. Don't do anything. + */ + + if (--textPtr->refCount == 0) { + ckfree((char *) textPtr); + } + return; + } + + lineNum = dInfoPtr->currentMetricUpdateLine; + if (lineNum == -1) { + dInfoPtr->lastMetricUpdateLine = 0; + } + /* + * Update the lines in blocks of about 24 recalculations, + * or 250+ lines examined, so we pass in 256 for 'doThisMuch'. + */ + lineNum = TkTextUpdateLineMetrics(textPtr, lineNum, + dInfoPtr->lastMetricUpdateLine, 256); + if (lineNum == dInfoPtr->lastMetricUpdateLine) { + /* + * We have looped over all lines, so we're done. We must + * release our refCount on the widget (the timer token + * was already set to NULL above). + */ + textPtr->refCount--; + if (textPtr->refCount == 0) { + ckfree((char *) textPtr); + } + return; + } + dInfoPtr->currentMetricUpdateLine = lineNum; + /* + * Re-arm the timer. We already have a refCount on the text widget + * so no need to adjust that. + */ + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + TkTextAsyncUpdateLineMetrics, (ClientData) textPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateLineMetrics -- + * + * This procedure updates the pixel height calculations of a + * range of lines in the widget. The range is from lineNum to + * endLine, but, if doThisMuch is positive, then the procedure + * may return earlier, once a certain number of lines has been + * examined. The line counts are from 0. + * + * If doThisMuch is -1, then all lines in the range will be + * updated. This will potentially take quite some time for + * a large text widget. + * + * Note: with bad input for lineNum and endLine, this procedure can + * loop indefinitely. + * + * Results: + * The index of the last line examined (or -1 if we are about to + * wrap around from end to beginning of the widget, and the next + * line will be the first line). + * + * Side effects: + * Line heights may be recalculated. + * + *---------------------------------------------------------------------- + */ + +int +TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) + TkText *textPtr; /* Information about widget. */ + int lineNum; /* Start at this line */ + int endLine; /* Go no further than this line */ + int doThisMuch; /* How many lines to check, or how many 10s of + * lines to recalculate. If '-1' then do + * everything in the range (which may take a + * while). */ +{ + TkTextLine *linePtr = NULL; + int count = 0; + int totalLines = TkBTreeNumLines(textPtr->tree); + + while (1) { + /* Get a suitable line */ + if (lineNum == -1 && linePtr == NULL) { + lineNum = 0; + linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + } else { + if (lineNum == -1 || linePtr == NULL) { + if (lineNum == -1) { + lineNum = 0; + } + linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + } else { + lineNum++; + linePtr = TkBTreeNextLine(linePtr); + } + if (lineNum == endLine) { + /* + * We have looped over all lines, so we're done. + */ + break; + } + } + + if (lineNum < totalLines) { + /* Now update the line's metrics if necessary */ + if (linePtr->pixelCalculationEpoch + != textPtr->dInfoPtr->lineMetricUpdateEpoch) { + /* + * Update the line and update the counter, counting + * 10 for each line we actually re-layout. + */ + TkTextUpdateOneLine(textPtr, linePtr); + count += 10; + } + } else { + /* + * We must never recalculate the height of the + * last artificial line. It must stay at zero, and + * if we recalculate it, it will change. + */ + + if (endLine >= totalLines) { + break; + } + /* Set things up for the next loop through */ + lineNum = -1; + } + count++; + + if (doThisMuch != -1 && count >= doThisMuch) { + break; + } + } + if (doThisMuch == -1) { + /* + * If we were requested to provide a full update, + * then also update the scrollbar. + */ + GetYView(textPtr->interp, textPtr, 1); + } + return lineNum; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextInvalidateLineMetrics -- + * + * Mark a number of text lines as having invalid line metric + * calculations. Never call this with linePtr as the last + * (artificial) line in the text. Depending on 'action' which + * indicates whether the given lines are simply invalid or have + * been inserted or deleted, the pre-existing asynchronous line + * update range may need to be adjusted. + * + * If linePtr is NULL then 'lineCount' and 'action' are ignored and + * all lines are invalidated. + * + * Results: + * None. + * + * Side effects: + * May schedule an asychronous callback. + * + *---------------------------------------------------------------------- + */ + +void +TkTextInvalidateLineMetrics(textPtr, linePtr, lineCount, action) + TkText *textPtr; /* Widget record for text widget. */ + TkTextLine *linePtr; /* Invalidation starts from this line. */ + int lineCount; /* And includes this many following + * lines. */ + int action; /* Indicates what type of invalidation + * occurred (insert, delete, or + * simple). */ +{ + int fromLine; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + if (linePtr != NULL) { + int counter = lineCount; + + fromLine = TkBTreeLineIndex(linePtr); + + /* + * Invalid the height calculations of each line in the + * given range. + */ + linePtr->pixelCalculationEpoch = 0; + while (counter > 0 && linePtr != 0) { + linePtr = TkBTreeNextLine(linePtr); + if (linePtr != NULL) { + linePtr->pixelCalculationEpoch = 0; + } + counter--; + } + /* + * Now schedule an examination of each line in the union of the + * old and new update ranges, including the (possibly empty) + * range in between. If that between range is not-empty, then we + * are examining more lines than is strictly necessary (but the + * examination of the extra lines should be quick, since their + * pixelCalculationEpoch will be up to date). However, to keep + * track of that would require more complex record-keeping that + * what we have. + */ + if (dInfoPtr->lineUpdateTimer == NULL) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (action == TK_TEXT_INVALIDATE_DELETE) { + lineCount = 0; + } + dInfoPtr->lastMetricUpdateLine = fromLine + lineCount + 1; + } else { + int toLine = fromLine + lineCount + 1; + + if (action == TK_TEXT_INVALIDATE_DELETE) { + if (toLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine -= lineCount; + } + } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (toLine <= dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine -= lineCount; + } + } else { + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } else if (action == TK_TEXT_INVALIDATE_INSERT) { + if (toLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine += lineCount; + } + } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (toLine <= dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine += lineCount; + } + if (toLine > dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } else { + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } else { + if (fromLine < dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + } + if (dInfoPtr->lastMetricUpdateLine != -1 + && toLine > dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } + } else { + /* + * This invalidates the height of all lines in the widget. + */ + if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) { + dInfoPtr->lineMetricUpdateEpoch++; + } + /* + * This has the effect of forcing an entire new loop + * of update checks on all lines in the widget. + */ + if (dInfoPtr->lineUpdateTimer == NULL) { + dInfoPtr->currentMetricUpdateLine = -1; + } + dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine; + } + + /* + * Now re-set the current update calculations + */ + if (dInfoPtr->lineUpdateTimer == NULL) { + textPtr->refCount++; + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + TkTextAsyncUpdateLineMetrics, (ClientData) textPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextFindDisplayLineEnd -- + * + * This procedure is invoked to find the index of the beginning or + * end of the particular display line on which the given index sits, + * whether that line is displayed or not. + * + * If 'end' is zero, we look for the start, and if 'end' is one + * we look for the end. + * + * If the beginning of the current display line is elided, and we + * are looking for the start of the line, then the returned index + * will be the first elided index on the display line. + * + * Similarly if the end of the current display line is elided + * and we are looking for the end, then the returned index will + * be the last elided index on the display line. (NB. This also + * highlights a current bug in the text widget that we cannot + * place two logical lines on a single display line -- even + * though the newline in this case is elided, it still causes + * a line break to be shown). + * + * Results: + * Modifies indexPtr to point to the given end. + * + * If xOffset is non-NULL, it is set to the x-pixel offset of the + * given original index within the given display line. + * + * Side effects: + * The combination of 'LayoutDLine' and 'FreeDLines' seems + * like a rather time-consuming way of gathering the information + * we need, so this would be a good place to look to speed up + * the calculations. In particular these calls will map and + * unmap embedded windows respectively, which I would hope isn't + * exactly necessary! + * + *---------------------------------------------------------------------- + */ + +void +TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset) + TkText *textPtr; /* Widget record for text widget. */ + TkTextIndex *indexPtr; /* Index we will adjust to the display + * line start or end. */ + int end; /* 0 = start, 1 = end. */ + int *xOffset; /* NULL, or used to store the x-pixel + * offset of the original index within + * its display line. */ +{ + if (!end && indexPtr->byteIndex == 0) { + /* Nothing to do */ + if (xOffset != NULL) { + *xOffset = 0; + } + return; + } else { + TkTextIndex index = *indexPtr; + index.byteIndex = 0; + index.textPtr = NULL; + + while (1) { + DLine *dlPtr; + int byteCount; + + dlPtr = LayoutDLine(textPtr, &index); + byteCount = dlPtr->byteCount; + + /* + * 'byteCount' goes up to the beginning of the next line, + * so equality here says we need one more line + */ + if (index.byteIndex + byteCount > indexPtr->byteIndex) { + /* It's on this display line */ + if (xOffset != NULL) { + /* + * This call takes a byte index relative to the + * start of the current _display_ line, not + * logical line. We are about to overwrite + * indexPtr->byteIndex, so we must do this now. + */ + *xOffset = DlineXOfIndex(textPtr, dlPtr, + indexPtr->byteIndex - dlPtr->index.byteIndex); + } + indexPtr->byteIndex = index.byteIndex; + if (end) { + /* + * The index we want is one less than the number + * of bytes in the display line. + */ + indexPtr->byteIndex += byteCount - sizeof(char); + } + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + return; + } + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + TkTextIndexForwBytes(&index, byteCount, &index); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TextCalculateDisplayLineHeight -- + * + * This procedure is invoked to recalculate the height of the + * particular display line which starts with the given index, + * whether that line is displayed or not. + * + * This procedure does not, in itself, update any cached + * information about line heights. That should be done, where + * necessary, by its callers. + * + * The behaviour of this procedure is _undefined_ if indexPtr + * is not currently at the beginning of a display line. + * + * Results: + * The number of vertical pixels used by the display line. + * + * If 'byteCountPtr' is non-NULL, then returns in that pointer + * the number of byte indices on the given display line (which + * can be used to update indexPtr in a loop). + * + * Side effects: + * The combination of 'LayoutDLine' and 'FreeDLines' seems + * like a rather time-consuming way of gathering the information + * we need, so this would be a good place to look to speed up + * the calculations. In particular these calls will map and + * unmap embedded windows respectively, which I would hope isn't + * exactly necessary! + * + *---------------------------------------------------------------------- + */ + +static int +TextCalculateDisplayLineHeight(textPtr, indexPtr, byteCountPtr) + TkText *textPtr; /* Widget record for text widget. */ + CONST TkTextIndex *indexPtr; /* The index at the beginning of the + * display line of interest. */ + int *byteCountPtr; /* NULL or used to return the number of + * byte indices on the given display + * line. */ +{ + DLine *dlPtr; + int pixelHeight; + + /* + * Layout, find the information we need and then free the + * display-line we laid-out. We must use 'FreeDLines' because it + * will actually call the relevant code to unmap any embedded windows + * which were mapped in the LayoutDLine call! + */ + dlPtr = LayoutDLine(textPtr, indexPtr); + pixelHeight = dlPtr->height; + if (byteCountPtr != NULL) { + *byteCountPtr = dlPtr->byteCount; + } + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + + return pixelHeight; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextIndexYPixels -- + * + * This procedure is invoked to calculate the number of vertical + * pixels between the first index of the text widget and the given + * index. The range from first logical line to given logical + * line is determined using the cached values, and the range + * inside the given logical line is calculated on the fly. + * + * Results: + * The pixel distance between first pixel in the widget and the + * top of the index's current display line (could be zero). + * + * Side effects: + * Just those of 'TextCalculateDisplayLineHeight'. + * + *---------------------------------------------------------------------- + */ + +int +TkTextIndexYPixels(textPtr, indexPtr) + TkText *textPtr; /* Widget record for text widget. */ + CONST TkTextIndex *indexPtr; /* The index of which we want the pixel + * distance from top of logical line to + * top of index. */ +{ + int pixelHeight; + TkTextIndex index; + + pixelHeight = TkBTreePixels(indexPtr->linePtr); + + /* + * Iterate through all display-lines corresponding to the single + * logical line belonging to indexPtr, adding up the pixel height of + * each such display line as we go along, until we go past + * 'indexPtr'. + */ + + if (indexPtr->byteIndex == 0) { + return pixelHeight; + } + + index.tree = textPtr->tree; + index.linePtr = indexPtr->linePtr; + index.byteIndex = 0; + index.textPtr = NULL; + + while (1) { + int bytes, height; + + /* + * Currently this call doesn't have many side-effects. + * However, if in the future we change the code so there + * are side-effects (such as adjusting linePtr->pixelHeight), + * then the code might not quite work as intended, + * specifically the 'linePtr->pixelHeight == pixelHeight' test + * below this while loop. + */ + height = TextCalculateDisplayLineHeight(textPtr, &index, &bytes); + + index.byteIndex += bytes; + + if (index.byteIndex > indexPtr->byteIndex) { + return pixelHeight; + } + + if (height > 0) { + pixelHeight += height; + } + + if (index.byteIndex == indexPtr->byteIndex) { + return pixelHeight; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateOneLine -- + * + * This procedure is invoked to recalculate the height of a + * particular logical line, whether that line is displayed or not. + * + * It must NEVER be called for the artificial last TkTextLine + * which is used internally for administrative purposes only. + * That line must retain its initial height of 0 otherwise + * the pixel height calculation maintained by the B-tree will + * be wrong. + * + * Results: + * The number of display lines in the logical line. This could + * be zero if the line is totally elided. + * + * Side effects: + * Line heights may be recalculated, and a timer to update + * the scrollbar may be installed. Also see the called + * function 'TextCalculateDisplayLineHeight' for its side + * effects. + * + *---------------------------------------------------------------------- + */ + +int +TkTextUpdateOneLine(textPtr, linePtr) + TkText *textPtr; /* Widget record for text widget. */ + TkTextLine *linePtr; /* The line of which to calculate the + * height. */ +{ + TkTextIndex index; + int pixelHeight, displayLines; + + index.tree = textPtr->tree; + index.linePtr = linePtr; + index.byteIndex = 0; + index.textPtr = NULL; + + /* + * Iterate through all display-lines corresponding to the + * single logical line 'linePtr', adding up the pixel height + * of each such display line as we go along. The final + * total is, therefore, the height of the logical line. + */ + + pixelHeight = 0; + displayLines = 0; + + while (1) { + int bytes, height; + + /* + * Currently this call doesn't have many side-effects. + * However, if in the future we change the code so there + * are side-effects (such as adjusting linePtr->pixelHeight), + * then the code might not quite work as intended, + * specifically the 'linePtr->pixelHeight == pixelHeight' test + * below this while loop. + */ + height = TextCalculateDisplayLineHeight(textPtr, &index, &bytes); + + if (height > 0) { + pixelHeight += height; + displayLines++; + } + + if (TkTextIndexForwBytes(&index, bytes, &index)) { + break; + } + + if (index.linePtr != linePtr) { + break; + } + } + + /* + * Mark the logical line as being up to date (caution: it isn't + * yet up to date, that will happen in TkBTreeAdjustPixelHeight + * just below). + */ + linePtr->pixelCalculationEpoch = textPtr->dInfoPtr->lineMetricUpdateEpoch; + + if (linePtr->pixelHeight == pixelHeight) { + return displayLines; + } + + /* + * We now use the resulting 'pixelHeight' to refer to the + * height of the entire widget, which may be used just below + * for reporting/debugging purposes + */ + pixelHeight = TkBTreeAdjustPixelHeight(linePtr, pixelHeight); + + if (tkTextDebug) { + char buffer[TCL_INTEGER_SPACE + 1]; + + if (TkBTreeNextLine(linePtr) == NULL) { + panic("Mustn't ever update line height of last artificial line"); + } + + sprintf(buffer, "%d", pixelHeight); + Tcl_SetVar2(textPtr->interp, "tk_textNumPixels", (char *) NULL, + buffer, TCL_GLOBAL_ONLY); + } + if (textPtr->dInfoPtr->scrollbarTimer == NULL) { + textPtr->refCount++; + textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200, + TkTextUpdateYScrollbar, (ClientData) textPtr); + } + return displayLines; +} + +/* + *---------------------------------------------------------------------- + * * DisplayText -- * * This procedure is invoked as a when-idle handler to update the @@ -2243,14 +3188,16 @@ DisplayText(clientData) */ while (dInfoPtr->flags & REPICK_NEEDED) { - Tcl_Preserve((ClientData) textPtr); + textPtr->refCount++; dInfoPtr->flags &= ~REPICK_NEEDED; TkTextPickCurrent(textPtr, &textPtr->pickEvent); + if (--textPtr->refCount == 0) { + ckfree((char *) textPtr); + goto end; + } if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { - Tcl_Release((ClientData) textPtr); goto end; } - Tcl_Release((ClientData) textPtr); } /* @@ -2262,7 +3209,10 @@ DisplayText(clientData) /* * See if it's possible to bring some parts of the screen up-to-date - * by scrolling (copying from other parts of the screen). + * by scrolling (copying from other parts of the screen). We have + * to be particularly careful with the top and bottom lines of the + * display, since these may only be partially visible and therefore + * not helpful for some scrolling purposes. */ for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { @@ -2270,8 +3220,30 @@ DisplayText(clientData) int offset, height, y, oldY; TkRegion damageRgn; - if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY) - || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) { + /* + * These tests are, in order: + * + * 1. If the line is already marked as invalid + * 2. If the line hasn't moved + * 3. If the line overlaps the bottom of the window and we + * are scrolling up + * 4. If the line overlaps the top of the window and we are + * scrolling down + * + * If any of these tests are true, then we can't scroll this + * line's part of the display. + * + * Note that even if tests 3 or 4 aren't true, we may be + * able to scroll the line, but we still need to be sure + * to call embedded window display procs on top and bottom + * lines if they have any portion non-visible (see below). + */ + if ((dlPtr->flags & OLD_Y_INVALID) + || (dlPtr->y == dlPtr->oldY) + || (((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY) + && (dlPtr->y < dlPtr->oldY)) + || ((dlPtr->oldY < dInfoPtr->y) + && (dlPtr->y > dlPtr->oldY))) { continue; } @@ -2286,7 +3258,7 @@ DisplayText(clientData) y = dlPtr->y; for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { - if ((dlPtr2->oldY == -1) + if ((dlPtr2->flags & OLD_Y_INVALID) || ((dlPtr2->oldY + offset) != dlPtr2->y) || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) { break; @@ -2298,18 +3270,29 @@ DisplayText(clientData) * Reduce the height of the area being copied if necessary to * avoid overwriting the border area. */ - if ((y + height) > dInfoPtr->maxY) { height = dInfoPtr->maxY -y; } oldY = dlPtr->oldY; - + if (y < dInfoPtr->y) { + /* + * Adjust if the area being copied is going to overwrite + * the top border of the window (so the top line is only + * half onscreen). + */ + int y_off = dInfoPtr->y - dlPtr->y; + height -= y_off; + oldY += y_off; + y = dInfoPtr->y; + } + /* * Update the lines we are going to scroll to show that they * have been copied. */ while (1) { + /* The DLine already has OLD_Y_INVALID cleared */ dlPtr->oldY = dlPtr->y; if (dlPtr->nextPtr == dlPtr2) { break; @@ -2324,10 +3307,10 @@ DisplayText(clientData) */ for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { - if ((dlPtr2->oldY != -1) + if ((!(dlPtr2->flags & OLD_Y_INVALID)) && ((dlPtr2->oldY + dlPtr2->height) > y) && (dlPtr2->oldY < (y + height))) { - dlPtr2->oldY = -1; + dlPtr2->flags |= OLD_Y_INVALID; } } @@ -2439,14 +3422,23 @@ DisplayText(clientData) maxHeight = -1; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) { + if ((dlPtr->height > maxHeight) + && ((dlPtr->flags & OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) { maxHeight = dlPtr->height; } bottomY = dlPtr->y + dlPtr->height; } - if (maxHeight > dInfoPtr->maxY) { - maxHeight = dInfoPtr->maxY; + /* + * There used to be a line here which restricted 'maxHeight' to be no + * larger than 'dInfoPtr->maxY', but this is incorrect for the case + * where individual lines may be taller than the widget _and_ we have + * smooth scrolling. What we can do is restrict maxHeight to be + * no larger than 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'. + */ + if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) { + maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset); } + if (maxHeight > 0) { pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), @@ -2455,7 +3447,7 @@ DisplayText(clientData) (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY); prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) { if (dlPtr->chunkPtr == NULL) continue; - if (dlPtr->oldY != dlPtr->y) { + if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) { if (tkTextDebug) { char string[TK_POS_CHARS]; TkTextPrintIndex(&dlPtr->index, string); @@ -2469,9 +3461,53 @@ DisplayText(clientData) return; } dlPtr->oldY = dlPtr->y; - dlPtr->flags &= ~NEW_LAYOUT; + dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID); + } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0) + || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) { + register TkTextDispChunk *chunkPtr; + + /* + * It's the first or last DLine which are also + * overlapping the top or bottom of the window, but we + * decided above it wasn't necessary to display them (we + * were able to update them by scrolling). This is fine, + * except that if the lines contain any embedded windows, + * we must still call the display proc on them because + * they might need to be unmapped or they might need to + * be moved to reflect their new position. Otherwise, + * everything else moves, but the embedded window + * doesn't! + * + * So, we loop through all the chunks, calling the + * display proc of embedded windows only. + */ + for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); + chunkPtr = chunkPtr->nextPtr) { + int x; + if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) { + continue; + } + x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; + if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { + /* + * Note: we have to call the displayProc even for + * chunks that are off-screen. This is needed, + * for example, so that embedded windows can be + * unmapped in this case. Display the chunk at a + * coordinate that can be clearly identified by + * the displayProc as being off-screen to the + * left (the displayProc may not be able to tell + * if something is off to the right). + */ + x = -chunkPtr->width; + } + TkTextEmbWinDisplayProc(chunkPtr, x, dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, (Display *) NULL, + (Drawable) None, dlPtr->y + dlPtr->spaceAbove); + } + } - /*prevPtr = dlPtr;*/ } Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); } @@ -2658,9 +3694,11 @@ TextInvalidateRegion(textPtr, region) maxY = rect.y + rect.height; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y, - rect.width, (unsigned int) dlPtr->height) != RectangleOut)) { - dlPtr->oldY = -1; + if ((!(dlPtr->flags & OLD_Y_INVALID)) + && (TkRectInRegion(region, rect.x, dlPtr->y, + rect.width, (unsigned int) dlPtr->height) + != RectangleOut)) { + dlPtr->flags |= OLD_Y_INVALID; } } if (dInfoPtr->topOfEof < maxY) { @@ -2693,6 +3731,10 @@ TextInvalidateRegion(textPtr, region) * changed). This procedure must be called *before* a change is * made, so that indexes in the display information are still * valid. + * + * Note: if the range of indices may change geometry as well + * as simply requiring redisplay, then the caller should also + * call TkTextInvalidateLineMetrics. * * Results: * None. @@ -2707,10 +3749,11 @@ TextInvalidateRegion(textPtr, region) void TkTextChanged(textPtr, index1Ptr, index2Ptr) - TkText *textPtr; /* Widget record for text widget. */ - TkTextIndex *index1Ptr; /* Index of first character to redisplay. */ - TkTextIndex *index2Ptr; /* Index of character just after last one - * to redisplay. */ + TkText *textPtr; /* Widget record for text widget. */ + CONST TkTextIndex *index1Ptr; /* Index of first character to + * redisplay. */ + CONST TkTextIndex *index2Ptr; /* Index of character just after last one + * to redisplay. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; DLine *firstPtr, *lastPtr; @@ -2763,7 +3806,7 @@ TkTextChanged(textPtr, index1Ptr, index2Ptr) * Delete all the DLines from firstPtr up to but not including lastPtr. */ - FreeDLines(textPtr, firstPtr, lastPtr, 1); + FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK); } /* @@ -2806,6 +3849,31 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) TkTextIndex *curIndexPtr; TkTextIndex endOfText, *endIndexPtr; + /* + * Invalidate the pixel calculation of all lines in the given range. + * This may be a bit over-aggressive, so we could consider more + * subtle techniques here in the future. In particular, when we + * create a tag for the first time with '.t tag configure foo -font + * "Arial 20"', say, even though that obviously can't apply to + * anything at all (the tag didn't exist a moment ago), we invalidate + * every single line in the widget. + */ + if (tagPtr->affectsDisplayGeometry) { + TkTextLine *startLine, *endLine; + if (index1Ptr == NULL) { + startLine = NULL; + } else { + startLine = index1Ptr->linePtr; + } + if (index2Ptr == NULL) { + endLine = NULL; + } else { + endLine = index2Ptr->linePtr; + } + TkTextInvalidateLineMetrics(textPtr, startLine, endLine - startLine, + TK_TEXT_INVALIDATE_ONLY); + } + /* * Round up the starting position if it's before the first line * visible on the screen (we only care about what's on the screen). @@ -2912,7 +3980,7 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) * be re-layed out and redrawn. */ - FreeDLines(textPtr, dlPtr, endPtr, 1); + FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK); dlPtr = endPtr; /* @@ -2947,8 +4015,10 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) */ void -TkTextRelayoutWindow(textPtr) +TkTextRelayoutWindow(textPtr, mask) TkText *textPtr; /* Widget record for text widget. */ + int mask; /* OR'd collection of bits showing what + * has changed */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; GC new; @@ -2981,7 +4051,7 @@ TkTextRelayoutWindow(textPtr) * Throw away all the current layout information. */ - FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); + FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, DLINE_UNLINK); dInfoPtr->dLinePtr = NULL; /* @@ -3016,7 +4086,7 @@ TkTextRelayoutWindow(textPtr) */ if (textPtr->topIndex.byteIndex != 0) { - MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex); + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); } /* @@ -3026,6 +4096,25 @@ TkTextRelayoutWindow(textPtr) dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1; dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1; + + if (mask & TK_TEXT_LINE_GEOMETRY) { + /* + * Set up line metric recalculation. + * + * Avoid the special zero value, since that is used to + * mark individual lines as being out of date. + */ + if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) { + dInfoPtr->lineMetricUpdateEpoch++; + } + dInfoPtr->currentMetricUpdateLine = -1; + + if (dInfoPtr->lineUpdateTimer == NULL) { + textPtr->refCount++; + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + TkTextAsyncUpdateLineMetrics, (ClientData) textPtr); + } + } } /* @@ -3052,17 +4141,27 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) TkText *textPtr; /* Widget record for text widget. */ TkTextIndex *indexPtr; /* Position that is to appear somewhere * in the view. */ - int pickPlace; /* 0 means topLine must appear at top of - * screen. 1 means we get to pick where it - * appears: minimize screen motion or else - * display line at center of screen. */ + int pickPlace; /* 0 means the given index must appear + * exactly at the top of the screen. + * TK_TEXT_PICKPLACE (-1) means we get to + * pick where it appears: minimize screen + * motion or else display line at center + * of screen. TK_TEXT_NOPIXELADJUST (-2) + * indicates to make the given index the + * top line, but if it is already the top + * line, don't nudge it up or down by a + * few pixels just to make sure it is + * entirely displayed. Positive numbers + * indicate the number of pixels of the + * index's line which are to be off the + * top of the screen. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; register DLine *dlPtr; int bottomY, close, lineIndex; TkTextIndex tmpIndex, rounded; - Tk_FontMetrics fm; - + int lineHeight; + /* * If the specified position is the extra line at the end of the * text, round it back to the last real line. @@ -3070,11 +4169,20 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) lineIndex = TkBTreeLineIndex(indexPtr->linePtr); if (lineIndex == TkBTreeNumLines(indexPtr->tree)) { - TkTextIndexBackChars(indexPtr, 1, &rounded); + TkTextIndexBackChars(indexPtr, 1, &rounded, COUNT_INDICES); indexPtr = &rounded; } - if (!pickPlace) { + if (pickPlace == -2) { + if (textPtr->topIndex.linePtr == indexPtr->linePtr + && textPtr->topIndex.byteIndex == indexPtr->byteIndex) { + pickPlace = dInfoPtr->topPixelOffset; + } else { + pickPlace = 0; + } + } + + if (pickPlace != -1) { /* * The specified position must go at the top of the screen. * Just leave all the DLine's alone: we may be able to reuse @@ -3082,11 +4190,11 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * without redisplaying it all. */ - if (indexPtr->byteIndex == 0) { - textPtr->topIndex = *indexPtr; - } else { - MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); + textPtr->topIndex = *indexPtr; + if (indexPtr->byteIndex != 0) { + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); } + dInfoPtr->newTopPixelOffset = pickPlace; goto scheduleUpdate; } @@ -3111,6 +4219,16 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) dlPtr = NULL; } else if ((dlPtr->index.linePtr == indexPtr->linePtr) && (dlPtr->index.byteIndex <= indexPtr->byteIndex)) { + if (dInfoPtr->dLinePtr == dlPtr + && dInfoPtr->topPixelOffset != 0) { + /* + * It is on the top line, but that line is hanging + * off the top of the screen. Change the top + * overlap to zero and update. + */ + dInfoPtr->newTopPixelOffset = 0; + goto scheduleUpdate; + } return; } } @@ -3119,37 +4237,50 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * The desired line isn't already on-screen. Figure out what * it means to be "close" to the top or bottom of the screen. * Close means within 1/3 of the screen height or within three - * lines, whichever is greater. Add one extra line also, to - * account for the way MeasureUp rounds. + * lines, whichever is greater. + * + * If the line is not close, place it in the center of the + * window. */ - Tk_GetFontMetrics(textPtr->tkfont, &fm); - bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2; + lineHeight = TextCalculateDisplayLineHeight(textPtr, indexPtr, NULL); + /* + * It would be better if 'bottomY' were calculated using the + * actual height of the given line, not 'textPtr->charHeight'. + */ + bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2; close = (dInfoPtr->maxY - dInfoPtr->y)/3; - if (close < 3*fm.linespace) { - close = 3*fm.linespace; + if (close < 3*textPtr->charHeight) { + close = 3*textPtr->charHeight; } - close += fm.linespace; if (dlPtr != NULL) { + int overlap; /* * The desired line is above the top of screen. If it is * "close" to the top of the window then make it the top - * line on the screen. + * line on the screen. MeasureUp counts from the bottom + * of the given index upwards, so we add an extra half line + * to be sure we count far enough. */ - MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex); + MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2, + &tmpIndex, &overlap); if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) { - MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); + textPtr->topIndex = *indexPtr; + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); + dInfoPtr->newTopPixelOffset = 0; goto scheduleUpdate; } } else { + int overlap; /* * The desired line is below the bottom of the screen. If it is * "close" to the bottom of the screen then position it at the * bottom of the screen. */ - MeasureUp(textPtr, indexPtr, close, &tmpIndex); + MeasureUp(textPtr, indexPtr, close + lineHeight + - textPtr->charHeight/2, &tmpIndex, &overlap); if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) { bottomY = dInfoPtr->maxY - dInfoPtr->y; } @@ -3163,7 +4294,8 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * is a half-line lower than the center of the window. */ - MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex); + MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex, + &dInfoPtr->newTopPixelOffset); scheduleUpdate: if (!(dInfoPtr->flags & REDRAW_PENDING)) { @@ -3175,11 +4307,72 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) /* *-------------------------------------------------------------- * - * MeasureUp -- + * TkTextMeasureDown -- * * Given one index, find the index of the first character * on the highest display line that would be displayed no more - * than "distance" pixels above the given index. + * than "distance" pixels below the top of the given index. + * + * Results: + * The srcPtr is manipulated in place to reflect the new + * position. We return the number of pixels by which 'distance' + * overlaps the srcPtr. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +int +TkTextMeasureDown(textPtr, srcPtr, distance) + TkText *textPtr; /* Text widget in which to measure. */ + TkTextIndex *srcPtr; /* Index of character from which to start + * measuring. */ + int distance; /* Vertical distance in pixels measured + * from the top pixel in srcPtr's + logical line. */ +{ + TkTextLine *lastLinePtr; + DLine *dlPtr; + TkTextIndex loop; + + lastLinePtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + + do { + dlPtr = LayoutDLine(textPtr, srcPtr); + dlPtr->nextPtr = NULL; + + if (distance < dlPtr->height) { + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP); + break; + } + distance -= dlPtr->height; + TkTextIndexForwBytes(srcPtr, dlPtr->byteCount, &loop); + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP); + if (loop.linePtr == lastLinePtr) { + break; + } + *srcPtr = loop; + } while (distance > 0); + + return distance; +} + +/* + *-------------------------------------------------------------- + * + * MeasureUp -- + * + * Given one index, find the index of the first character on the + * highest display line that would be displayed no more than + * "distance" pixels above the given index. + * + * If this function is called with distance=0, it simply finds the + * first index on the same display line as srcPtr. However, there + * is a another function TkTextFindDisplayLineEnd designed just for + * that task which is probably better to use. * * Results: * *dstPtr is filled in with the index of the first character @@ -3187,9 +4380,8 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * up "distance" pixels above the pixel just below an imaginary * display line that contains srcPtr. If the display line * that covers this coordinate actually extends above the - * coordinate, then return the index of the next lower line - * instead (i.e. the returned index will be completely visible - * at or below the given y-coordinate). + * coordinate, then return any excess pixels in *overlap, if + * that is non-NULL. * * Side effects: * None. @@ -3198,30 +4390,31 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) */ static void -MeasureUp(textPtr, srcPtr, distance, dstPtr) +MeasureUp(textPtr, srcPtr, distance, dstPtr, overlap) TkText *textPtr; /* Text widget in which to measure. */ - TkTextIndex *srcPtr; /* Index of character from which to start + CONST TkTextIndex *srcPtr; /* Index of character from which to start * measuring. */ int distance; /* Vertical distance in pixels measured * from the pixel just below the lowest * one in srcPtr's line. */ TkTextIndex *dstPtr; /* Index to fill in with result. */ + int *overlap; /* Used to store how much of the final + * index returned was not covered by + * 'distance'. */ { int lineNum; /* Number of current line. */ int bytesToCount; /* Maximum number of bytes to measure in * current line. */ - TkTextIndex bestIndex; /* Best candidate seen so far for result. */ TkTextIndex index; DLine *dlPtr, *lowestPtr; - int noBestYet; /* 1 means bestIndex hasn't been set. */ - noBestYet = 1; bytesToCount = srcPtr->byteIndex + 1; index.tree = srcPtr->tree; for (lineNum = TkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0; lineNum--) { /* * Layout an entire text line (potentially > 1 display line). + * * For the first line, which contains srcPtr, only layout the * part up through srcPtr (bytesToCount is non-infinite to * accomplish this). Make a list of all the display lines @@ -3243,17 +4436,20 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr) /* * Scan through the display lines to see if we've covered enough * vertical distance. If so, save the starting index for the - * line at the desired location. + * line at the desired location. If distance was zero to start + * with then we simply get the first index on the same display + * line as the original index. */ for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { distance -= dlPtr->height; - if (distance < 0) { - *dstPtr = (noBestYet) ? dlPtr->index : bestIndex; + if (distance <= 0) { + *dstPtr = dlPtr->index; + if (overlap != NULL) { + *overlap = -distance; + } break; } - bestIndex = dlPtr->index; - noBestYet = 0; } /* @@ -3261,8 +4457,8 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr) * for the next display line to lay out. */ - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); - if (distance < 0) { + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE); + if (distance <= 0) { return; } bytesToCount = INT_MAX; /* Consider all chars. in next line. */ @@ -3274,6 +4470,9 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr) */ TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr); + if (overlap != NULL) { + *overlap = 0; + } } /* @@ -3310,8 +4509,7 @@ TkTextSeeCmd(textPtr, interp, objc, objv) TkTextDispChunk *chunkPtr; if (objc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - Tcl_GetString(objv[0]), " see index\"", (char *) NULL); + Tcl_WrongNumArgs(interp, 2, objv, "index"); return TCL_ERROR; } if (TkTextGetObjIndex(interp, textPtr, objv[2], &index) != TCL_OK) { @@ -3324,14 +4522,14 @@ TkTextSeeCmd(textPtr, interp, objc, objv) */ if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) { - TkTextIndexBackChars(&index, 1, &index); + TkTextIndexBackChars(&index, 1, &index, COUNT_INDICES); } /* * First get the desired position into the vertical range of the window. */ - TkTextSetYView(textPtr, &index, 1); + TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE); /* * Now make sure that the character is in view horizontally. @@ -3376,24 +4574,24 @@ TkTextSeeCmd(textPtr, interp, objc, objv) dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, &height); - delta = x - dInfoPtr->curPixelOffset; + delta = x - dInfoPtr->curXPixelOffset; oneThird = lineWidth/3; if (delta < 0) { if (delta < -oneThird) { - dInfoPtr->newByteOffset = (x - lineWidth/2) + dInfoPtr->newXByteOffset = (x - lineWidth/2) / textPtr->charWidth; } else { - dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1) + dInfoPtr->newXByteOffset -= ((-delta) + textPtr->charWidth - 1) / textPtr->charWidth; } } else { delta -= (lineWidth - width); if (delta > 0) { if (delta > oneThird) { - dInfoPtr->newByteOffset = (x - lineWidth/2) + dInfoPtr->newXByteOffset = (x - lineWidth/2) / textPtr->charWidth; } else { - dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1) + dInfoPtr->newXByteOffset += (delta + textPtr->charWidth - 1) / textPtr->charWidth; } } else { @@ -3449,7 +4647,7 @@ TkTextXviewCmd(textPtr, interp, objc, objv) return TCL_OK; } - newOffset = dInfoPtr->newByteOffset; + newOffset = dInfoPtr->newXByteOffset; type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count); switch (type) { case TK_SCROLL_ERROR: @@ -3461,8 +4659,8 @@ TkTextXviewCmd(textPtr, interp, objc, objv) if (fraction < 0) { fraction = 0; } - newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth) - + 0.5); + newOffset = (int) (((fraction * dInfoPtr->maxLength) + / textPtr->charWidth) + 0.5); break; case TK_SCROLL_PAGES: charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth) @@ -3477,7 +4675,7 @@ TkTextXviewCmd(textPtr, interp, objc, objv) break; } - dInfoPtr->newByteOffset = newOffset; + dInfoPtr->newXByteOffset = newOffset; dInfoPtr->flags |= DINFO_OUT_OF_DATE; if (!(dInfoPtr->flags & REDRAW_PENDING)) { dInfoPtr->flags |= REDRAW_PENDING; @@ -3489,7 +4687,87 @@ TkTextXviewCmd(textPtr, interp, objc, objv) /* *---------------------------------------------------------------------- * - * ScrollByLines -- + * YScrollByPixels -- + * + * This procedure is called to scroll a text widget up or down + * by a given number of pixels. + * + * Results: + * None. + * + * Side effects: + * The view in textPtr's window changes to reflect the value + * of "offset". + * + *---------------------------------------------------------------------- + */ + +static void +YScrollByPixels(textPtr, offset) + TkText *textPtr; /* Widget to scroll. */ + int offset; /* Amount by which to scroll, in + * pixels. Positive means that information + * later in text becomes visible, negative + * means that information earlier in the + * text becomes visible. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + if (offset < 0) { + /* + * Now we want to measure up this number of pixels + * from the top of the screen. But the top line may + * not be totally visible. Note that 'count' is + * negative here. + */ + offset -= TextCalculateDisplayLineHeight(textPtr, + &textPtr->topIndex, NULL) - dInfoPtr->topPixelOffset; + MeasureUp(textPtr, &textPtr->topIndex, -offset, + &textPtr->topIndex, &dInfoPtr->newTopPixelOffset); + } else if (offset > 0) { + DLine *dlPtr; + TkTextLine *lastLinePtr; + TkTextIndex new; + /* + * Scrolling down by pixels. Layout lines starting at + * the top index and count through the desired vertical + * distance. + */ + + lastLinePtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + offset += dInfoPtr->topPixelOffset; + dInfoPtr->newTopPixelOffset = 0; + while (offset > 0) { + dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); + dlPtr->nextPtr = NULL; + TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, + &new); + if (offset <= dlPtr->height) { + /* Adjust the top overlap accordingly */ + dInfoPtr->newTopPixelOffset = offset; + } + offset -= dlPtr->height; + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP); + if (new.linePtr == lastLinePtr || offset <= 0) { + break; + } + textPtr->topIndex = new; + } + } else { + /* offset = 0, so no scrolling required */ + return; + } + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; +} + +/* + *---------------------------------------------------------------------- + * + * YScrollByLines -- * * This procedure is called to scroll a text widget up or down * by a given number of lines. @@ -3505,9 +4783,9 @@ TkTextXviewCmd(textPtr, interp, objc, objv) */ static void -ScrollByLines(textPtr, offset) +YScrollByLines(textPtr, offset) TkText *textPtr; /* Widget to scroll. */ - int offset; /* Amount by which to scroll, in *screen* + int offset; /* Amount by which to scroll, in display * lines. Positive means that information * later in text becomes visible, negative * means that information earlier in the @@ -3556,7 +4834,7 @@ ScrollByLines(textPtr, offset) * for the next display line to lay out. */ - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE); if (offset >= 0) { goto scheduleUpdate; } @@ -3565,10 +4843,12 @@ ScrollByLines(textPtr, offset) /* * Ran off the beginning of the text. Return the first character - * in the text. + * in the text, and make sure we haven't left anything + * overlapping the top window border. */ TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex); + dInfoPtr->newTopPixelOffset = 0; } else { /* * Scrolling down, to show later information in the text. @@ -3582,7 +4862,7 @@ ScrollByLines(textPtr, offset) if (dlPtr->length == 0 && dlPtr->height == 0) offset++; dlPtr->nextPtr = NULL; TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new); - FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE); if (new.linePtr == lastLinePtr) { break; } @@ -3625,14 +4905,11 @@ TkTextYviewCmd(textPtr, interp, objc, objv) * objv[1] is "yview". */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; - int pickPlace, lineNum, type, bytesInLine; - Tk_FontMetrics fm; + int pickPlace, type; int pixels, count; size_t switchLength; double fraction; - TkTextIndex index, new; - TkTextLine *lastLinePtr; - DLine *dlPtr; + TkTextIndex index; if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { UpdateDisplayInfo(textPtr); @@ -3651,18 +4928,17 @@ TkTextYviewCmd(textPtr, interp, objc, objv) if (Tcl_GetString(objv[2])[0] == '-') { switchLength = strlen(Tcl_GetString(objv[2])); if ((switchLength >= 2) - && (strncmp(Tcl_GetString(objv[2]), "-pickplace", switchLength) == 0)) { + && (strncmp(Tcl_GetString(objv[2]), + "-pickplace", switchLength) == 0)) { pickPlace = 1; if (objc != 4) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - Tcl_GetString(objv[0]), - " yview -pickplace lineNum|index\"", - (char *) NULL); + Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index"); return TCL_ERROR; } } } if ((objc == 3) || pickPlace) { + int lineNum; if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) { TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); TkTextSetYView(textPtr, &index, 0); @@ -3678,7 +4954,7 @@ TkTextYviewCmd(textPtr, interp, objc, objv) &index) != TCL_OK) { return TCL_ERROR; } - TkTextSetYView(textPtr, &index, pickPlace); + TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0)); return TCL_OK; } @@ -3686,79 +4962,67 @@ TkTextYviewCmd(textPtr, interp, objc, objv) * New syntax: dispatch based on objv[2]. */ - type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count); + type = TextGetScrollInfoObj(interp, textPtr, objc, objv, + &fraction, &count); switch (type) { - case TK_SCROLL_ERROR: + case TKTEXT_SCROLL_ERROR: return TCL_ERROR; - case TK_SCROLL_MOVETO: + case TKTEXT_SCROLL_MOVETO: if (fraction > 1.0) { fraction = 1.0; } if (fraction < 0) { fraction = 0; } - fraction *= TkBTreeNumLines(textPtr->tree); - lineNum = (int) fraction; - TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); - bytesInLine = TkBTreeBytesInLine(index.linePtr); - index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5); - if (index.byteIndex >= bytesInLine) { - TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index); - } - TkTextSetYView(textPtr, &index, 0); + fraction *= (TkBTreeNumPixels(textPtr->tree)-1); + /* + * This function returns the number of pixels by which the + * given line should overlap the top of the visible screen. + * + * This is then used to provide smooth scrolling. + */ + pixels = TkTextMakePixelIndex(textPtr, (int) (0.5 + fraction), + &index); + TkTextSetYView(textPtr, &index, pixels); break; - case TK_SCROLL_PAGES: + case TKTEXT_SCROLL_PAGES: { /* * Scroll up or down by screenfuls. Actually, use the * window height minus two lines, so that there's some * overlap between adjacent pages. */ - - Tk_GetFontMetrics(textPtr->tkfont, &fm); - if (count < 0) { - pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count) - + fm.linespace; - MeasureUp(textPtr, &textPtr->topIndex, pixels, &new); - if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) { - /* - * A page of scrolling ended up being less than one line. - * Scroll one line anyway. + int height = dInfoPtr->maxY - dInfoPtr->y; + if (textPtr->charHeight * 4 >= height) { + /* + * A single line is more than a quarter of the + * display. We choose to scroll by 3/4 of the + * height instead. + */ + pixels = 3*height/4; + if (pixels < textPtr->charHeight) { + /* + * But, if 3/4 of the height is actually less than a + * single typical character height, then scroll by + * the minimum of the linespace or the total height. */ - - count = -1; - goto scrollByLines; + if (textPtr->charHeight < height) { + pixels = textPtr->charHeight; + } else { + pixels = height; + } } - textPtr->topIndex = new; + pixels *= count; } else { - /* - * Scrolling down by pages. Layout lines starting at the - * top index and count through the desired vertical distance. - */ - - pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count; - lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); - do { - dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); - dlPtr->nextPtr = NULL; - TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, - &new); - pixels -= dlPtr->height; - FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); - if (new.linePtr == lastLinePtr) { - break; - } - textPtr->topIndex = new; - } while (pixels > 0); + pixels = (height - 2*textPtr->charHeight)*count; } - if (!(dInfoPtr->flags & REDRAW_PENDING)) { - Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); - } - dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; + YScrollByPixels(textPtr, pixels); break; - case TK_SCROLL_UNITS: - scrollByLines: - ScrollByLines(textPtr, count); + } + case TKTEXT_SCROLL_PIXELS: + YScrollByPixels(textPtr, count); + break; + case TKTEXT_SCROLL_UNITS: + YScrollByLines(textPtr, count); break; } return TCL_OK; @@ -3794,13 +5058,13 @@ TkTextScanCmd(textPtr, interp, objc, objv) TextDInfo *dInfoPtr = textPtr->dInfoPtr; TkTextIndex index; int c, x, y, totalScroll, newByte, maxByte, gain=10; - Tk_FontMetrics fm; size_t length; if ((objc != 5) && (objc != 6)) { Tcl_AppendResult(interp, "wrong # args: should be \"", Tcl_GetString(objv[0]), " scan mark x y\" or \"", - Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", (char *) NULL); + Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", + (char *) NULL); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) { @@ -3809,8 +5073,9 @@ TkTextScanCmd(textPtr, interp, objc, objv) if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) { return TCL_ERROR; } - if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) - return TCL_ERROR; + if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) { + return TCL_ERROR; + } c = Tcl_GetString(objv[2])[0]; length = strlen(Tcl_GetString(objv[2])); if ((c == 'd') && (strncmp(Tcl_GetString(objv[2]), "dragto", length) == 0)) { @@ -3839,24 +5104,24 @@ TkTextScanCmd(textPtr, interp, objc, objv) dInfoPtr->scanMarkIndex = maxByte; dInfoPtr->scanMarkX = x; } - dInfoPtr->newByteOffset = newByte; + dInfoPtr->newXByteOffset = newByte; - Tk_GetFontMetrics(textPtr->tkfont, &fm); - totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace; - if (totalScroll != dInfoPtr->scanTotalScroll) { + totalScroll = gain*(dInfoPtr->scanMarkY - y); + if (totalScroll != dInfoPtr->scanTotalYScroll) { index = textPtr->topIndex; - ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll); - dInfoPtr->scanTotalScroll = totalScroll; + YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll); + dInfoPtr->scanTotalYScroll = totalScroll; if ((index.linePtr == textPtr->topIndex.linePtr) && (index.byteIndex == textPtr->topIndex.byteIndex)) { - dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanTotalYScroll = 0; dInfoPtr->scanMarkY = y; } } - } else if ((c == 'm') && (strncmp(Tcl_GetString(objv[2]), "mark", length) == 0)) { - dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset; + } else if ((c == 'm') + && (strncmp(Tcl_GetString(objv[2]), "mark", length) == 0)) { + dInfoPtr->scanMarkIndex = dInfoPtr->newXByteOffset; dInfoPtr->scanMarkX = x; - dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanTotalYScroll = 0; dInfoPtr->scanMarkY = y; } else { Tcl_AppendResult(interp, "bad scan option \"", Tcl_GetString(objv[2]), @@ -3906,12 +5171,12 @@ GetXView(interp, textPtr, report) * scrollbar if it has changed. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; - char buffer[TCL_DOUBLE_SPACE * 2 + 1]; double first, last; int code; - + Tcl_Obj *listObj; + if (dInfoPtr->maxLength > 0) { - first = ((double) dInfoPtr->curPixelOffset) + first = ((double) dInfoPtr->curXPixelOffset) / dInfoPtr->maxLength; last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x)) / dInfoPtr->maxLength; @@ -3923,8 +5188,10 @@ GetXView(interp, textPtr, report) last = 1.0; } if (!report) { - sprintf(buffer, "%g %g", first, last); - Tcl_SetResult(interp, buffer, TCL_VOLATILE); + listObj = Tcl_NewListObj(0,NULL); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first)); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + Tcl_SetObjResult(interp, listObj); return; } if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) && @@ -3933,19 +5200,67 @@ GetXView(interp, textPtr, report) } dInfoPtr->xScrollFirst = first; dInfoPtr->xScrollLast = last; - sprintf(buffer, " %g %g", first, last); - code = Tcl_VarEval(interp, textPtr->xScrollCmd, - buffer, (char *) NULL); - if (code != TCL_OK) { - Tcl_AddErrorInfo(interp, + if (textPtr->xScrollCmd != NULL) { + listObj = Tcl_NewStringObj(textPtr->xScrollCmd, -1); + code = Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first)); + if (code == TCL_OK) { + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + code = Tcl_EvalObjEx(interp, listObj, + TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL); + } + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, "\n (horizontal scrolling command executed by text)"); - Tcl_BackgroundError(interp); + Tcl_BackgroundError(interp); + } } } /* *---------------------------------------------------------------------- * + * GetPixelCount -- + * + * How many pixels are there between the absolute top of the + * widget and the top of the given DLine. + * + * Results: + * The number of pixels. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetPixelCount(textPtr, dlPtr) + TkText *textPtr; /* Information about text widget. */ + DLine *dlPtr; /* Information about the layout + * of a given index */ +{ + TkTextLine *linePtr = dlPtr->index.linePtr; + /* + * Get the pixel count of one pixel beyond the + * botton of the given line. + */ + int count = TkBTreePixels(linePtr) + linePtr->pixelHeight; + + /* + * Now we have to subtract off the distance between the top of this + * dlPtr and the next logical line. + */ + do { + count -= dlPtr->height; + dlPtr = dlPtr->nextPtr; + } while (dlPtr != NULL && (dlPtr->index.linePtr == linePtr)); + + return count; +} + +/* + *---------------------------------------------------------------------- + * * GetYView -- * * This procedure computes the fractions that indicate what's @@ -3967,7 +5282,7 @@ GetXView(interp, textPtr, report) * *---------------------------------------------------------------------- */ - + static void GetYView(interp, textPtr, report) Tcl_Interp *interp; /* If "report" is FALSE, string @@ -3978,53 +5293,153 @@ GetYView(interp, textPtr, report) * scrollbar if it has changed. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; - char buffer[TCL_DOUBLE_SPACE * 2 + 1]; double first, last; DLine *dlPtr; - int totalLines, code, count; - + int totalPixels, code, count; + Tcl_Obj *listObj; + dlPtr = dInfoPtr->dLinePtr; - totalLines = TkBTreeNumLines(textPtr->tree); - first = (double) TkBTreeLineIndex(dlPtr->index.linePtr) - + (double) dlPtr->index.byteIndex - / TkBTreeBytesInLine(dlPtr->index.linePtr); - first /= totalLines; + + if (dlPtr == NULL) { + return; + } + + totalPixels = TkBTreeNumPixels(textPtr->tree); + + /* + * Get the pixel count for the first display visible pixel of the + * first visible line. If the first visible line is only partially + * visible, then we use 'topPixelOffset' to get the difference. + */ + count = GetPixelCount(textPtr, dlPtr); + first = ((double) (count + dInfoPtr->topPixelOffset))/((double)totalPixels); + + /* + * Add on the total number of visible pixels to get the count to + * the last visible pixel. + */ while (1) { - if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { + int extra; + count += dlPtr->height; + + extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY; + if (extra > 0) { /* - * The last line is only partially visible, so don't - * count its characters in what's visible. + * This much of the last line is not visible, so don't + * count these pixels. Since we've reached the bottom + * of the window, we break out of the loop. */ - count = 0; + count -= extra; break; } if (dlPtr->nextPtr == NULL) { - count = dlPtr->byteCount; break; } dlPtr = dlPtr->nextPtr; } - last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr)) - + ((double) (dlPtr->index.byteIndex + count)) - / (TkBTreeBytesInLine(dlPtr->index.linePtr)); - last /= totalLines; + + if (count > totalPixels) { + /* + * It can be possible, if we do not update each line's + * pixelHeight cache when we lay out individual DLines that + * the count generated here is more up-to-date than that + * maintained by the BTree. In such a case, the best we can + * do here is to fix up 'count' and continue, which might + * result in small, temporary perturbations to the size of + * the scrollbar. This is basically harmless, but in a + * perfect world we would not have this problem. + * + * For debugging purposes, if anyone wishes to improve the text + * widget further, the following 'panic' can be activated. In + * principle it should be possible to ensure the BTree is always + * at least as up to date as the display, so in the future we + * might be able to leave the 'panic' in permanently when we + * believe we have resolved the cache synchronisation issue. + * + * However, to achieve that goal would, I think, require a + * fairly substantial refactorisation of the code in this + * file so that there is much more obvious and explicit + * coordination between calls to LayoutDLine and updating + * of each TkTextLine's pixelHeight. The complicated bit + * is that LayoutDLine deals with individual display lines, + * but pixelHeight is for a logical line. + */ +#if 0 + char buffer[200]; + + sprintf(buffer, + "Counted more pixels (%d) than expected (%d) total pixels in text widget scroll bar calculation.", + count, totalPixels); + panic(buffer); +#endif + count = totalPixels; + } + + last = ((double) (count))/((double)totalPixels); + if (!report) { - sprintf(buffer, "%g %g", first, last); - Tcl_SetResult(interp, buffer, TCL_VOLATILE); + listObj = Tcl_NewListObj(0,NULL); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first)); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + Tcl_SetObjResult(interp, listObj); return; } - if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalLines) && - FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalLines)) { + if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) && + FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) { return; } dInfoPtr->yScrollFirst = first; dInfoPtr->yScrollLast = last; - sprintf(buffer, " %g %g", first, last); - code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL); - if (code != TCL_OK) { - Tcl_AddErrorInfo(interp, - "\n (vertical scrolling command executed by text)"); - Tcl_BackgroundError(interp); + if (textPtr->yScrollCmd != NULL) { + listObj = Tcl_NewStringObj(textPtr->yScrollCmd, -1); + code = Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewDoubleObj(first)); + if (code == TCL_OK) { + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + code = Tcl_EvalObjEx(interp, listObj, + TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL); + } + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, + "\n (vertical scrolling command executed by text)"); + Tcl_BackgroundError(interp); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateYScrollbar -- + * + * This procedure is called to update the vertical scrollbar + * asychronously as the pixel height calculations progress for + * lines in the widget. + * + * Results: + * None. + * + * Side effects: + * See 'GetYView'. In particular the scrollbar position and size + * may be changed. + * + *---------------------------------------------------------------------- + */ + +void +TkTextUpdateYScrollbar(clientData) + ClientData clientData; /* Information about widget. */ +{ + register TkText *textPtr = (TkText *) clientData; + + textPtr->dInfoPtr->scrollbarTimer = NULL; + + if (!(textPtr->flags & DESTROYED)) { + GetYView(textPtr->interp, textPtr, 1); + } + + if (--textPtr->refCount == 0) { + ckfree((char *) textPtr); } } @@ -4128,8 +5543,7 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) * index of the character nearest to (x,y). */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; - register DLine *dlPtr, *validdlPtr; - register TkTextDispChunk *chunkPtr; + register DLine *dlPtr, *validDlPtr; /* * Make sure that all of the layout information about what's @@ -4161,9 +5575,10 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) * Find the display line containing the desired y-coordinate. */ - for (dlPtr = validdlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height); - dlPtr = dlPtr->nextPtr) { - if (dlPtr->chunkPtr !=NULL) validdlPtr = dlPtr; + for (dlPtr = validDlPtr = dInfoPtr->dLinePtr; + y >= (dlPtr->y + dlPtr->height); + dlPtr = dlPtr->nextPtr) { + if (dlPtr->chunkPtr !=NULL) validDlPtr = dlPtr; if (dlPtr->nextPtr == NULL) { /* * Y-coordinate is off the bottom of the displayed text. @@ -4174,8 +5589,43 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) break; } } - if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr; + if (dlPtr->chunkPtr == NULL) dlPtr = validDlPtr; + + DlineIndexOfX(textPtr, dlPtr, x, indexPtr); +} + +/* + *---------------------------------------------------------------------- + * + * DlineIndexOfX -- + * + * Given an x coordinate in a display line, find the index of + * the character closest to that location. + * + * This is effectively the opposite of DlineXOfIndex. + * + * Results: + * The index at *indexPtr is modified to refer to the character + * on the display line that is closest to x. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +static void +DlineIndexOfX(textPtr, dlPtr, x, indexPtr) + TkText *textPtr; /* Widget record for text widget. */ + DLine *dlPtr; /* Display information for this + * display line. */ + int x; /* Pixel x coordinate of point in widget's + * window. */ + TkTextIndex *indexPtr; /* This index gets filled in with the + * index of the character nearest to x. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register TkTextDispChunk *chunkPtr; /* * Scan through the line's chunks to find the one that contains @@ -4185,13 +5635,13 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) */ *indexPtr = dlPtr->index; - x = x - dInfoPtr->x + dInfoPtr->curPixelOffset; + x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset; for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width); indexPtr->byteIndex += chunkPtr->numBytes, chunkPtr = chunkPtr->nextPtr) { if (chunkPtr->nextPtr == NULL) { indexPtr->byteIndex += chunkPtr->numBytes; - TkTextIndexBackChars(indexPtr, 1, indexPtr); + TkTextIndexBackChars(indexPtr, 1, indexPtr, COUNT_INDICES); return; } } @@ -4209,6 +5659,110 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) /* *---------------------------------------------------------------------- * + * TkTextIndexOfX -- + * + * Given a logical x coordinate (i.e. distance in pixels from the + * beginning of the display line, not taking into account any + * information about the window, scrolling etc.) on the display + * line starting with the given index, adjust that index to refer to + * the object under the x coordinate. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkTextIndexOfX(textPtr, x, indexPtr) + TkText *textPtr; /* Widget record for text widget. */ + int x; /* The x coordinate for which we want + * the index */ + TkTextIndex *indexPtr; /* Index of display line start, which + * will be adjusted to the index under the + * given x coordinate. */ +{ + DLine *dlPtr = LayoutDLine(textPtr, indexPtr); + DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x + - textPtr->dInfoPtr->curXPixelOffset, indexPtr); + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); +} + +/* + *---------------------------------------------------------------------- + * + * DlineXOfIndex -- + * + * Given a relative byte index on a given display line (i.e. the + * number of byte indices from the beginning of the given display + * line), find the x coordinate of that index within the abstract + * display line, without adjusting for the x-scroll state of the + * line. + * + * This is effectively the opposite of DlineIndexOfX. + * + * NB. The 'byteIndex' is relative to the display line, NOT the + * logical line. + * + * Results: + * The x coordinate. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +DlineXOfIndex(textPtr, dlPtr, byteIndex) + TkText *textPtr; /* Widget record for text widget. */ + DLine *dlPtr; /* Display information for this + * display line. */ + int byteIndex; /* The byte index for which we want the + * coordinate. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register TkTextDispChunk *chunkPtr; + TkTextIndex index; + int x; + + if (byteIndex == 0) return 0; + + /* + * Scan through the line's chunks to find the one that contains + * the desired byte index. + */ + + index = dlPtr->index; + chunkPtr = dlPtr->chunkPtr; + while (byteIndex > 0) { + if (byteIndex < chunkPtr->numBytes) { + int y, width, height; + (*chunkPtr->bboxProc)(chunkPtr, byteIndex, + dlPtr->y + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, + &height); + break; + } else { + byteIndex -= chunkPtr->numBytes; + } + if (chunkPtr->nextPtr == NULL || byteIndex == 0) { + x = chunkPtr->x + chunkPtr->width; + break; + } + chunkPtr = chunkPtr->nextPtr; + } + + return x; +} + +/* + *---------------------------------------------------------------------- + * * TkTextCharBbox -- * * Given an index, find the bounding box of the screen area @@ -4228,13 +5782,18 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) */ int -TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr) +TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, charWidthPtr) TkText *textPtr; /* Widget record for text widget. */ CONST TkTextIndex *indexPtr;/* Index of character whose bounding * box is desired. */ int *xPtr, *yPtr; /* Filled with character's upper-left * coordinate. */ int *widthPtr, *heightPtr; /* Filled in with character's dimensions. */ + int *charWidthPtr; /* If the 'character' isn't really a + * character (e.g. end of a line) and + * therefore takes up a very large + * width, this is used to return a + * smaller width */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; DLine *dlPtr; @@ -4286,17 +5845,24 @@ TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr) dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr, heightPtr); - *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset; + *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset; if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) { /* * Last character in display line. Give it all the space up to * the line. */ + if (charWidthPtr != NULL) { + *charWidthPtr = dInfoPtr->maxX - *xPtr; + } if (*xPtr > dInfoPtr->maxX) { *xPtr = dInfoPtr->maxX; } *widthPtr = dInfoPtr->maxX - *xPtr; + } else { + if (charWidthPtr != NULL) { + *charWidthPtr = *widthPtr; + } } if ((*xPtr + *widthPtr) <= dInfoPtr->x) { return -1; @@ -4369,7 +5935,7 @@ TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr) } dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0); - *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx; + *xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx; *widthPtr = dlPtr->length - dlx; *yPtr = dlPtr->y; if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { @@ -4454,8 +6020,9 @@ TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes, * many characters. */ int noCharsYet; /* Non-zero means no characters have been * assigned to this display line yet. */ - TkWrapMode wrapMode; /* How to handle line wrapping: TEXT_WRAPMODE_CHAR, - * TEXT_WRAPMODE_NONE, or TEXT_WRAPMODE_WORD. */ + TkWrapMode wrapMode; /* How to handle line wrapping: + * TEXT_WRAPMODE_CHAR, * TEXT_WRAPMODE_NONE, + * or TEXT_WRAPMODE_WORD. */ register TkTextDispChunk *chunkPtr; /* Structure to fill in with information * about this chunk. The x field has already @@ -4643,7 +6210,8 @@ CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) * Draw the text, underline, and overstrike for this chunk. */ - if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) { + if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) + && (stylePtr->fgGC != None)) { int numBytes = ciPtr->numBytes - offsetBytes; char *string = ciPtr->chars + offsetBytes; @@ -4759,7 +6327,7 @@ static void CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr, widthPtr, heightPtr) TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ - int byteIndex; /* Byte offset of desired character + int byteIndex; /* Byte offset of desired character * within the chunk. */ int y; /* Topmost pixel in area allocated * for this line. */ @@ -5235,3 +6803,102 @@ MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr) *nextXPtr = curX; return start - source; } + +/* + *---------------------------------------------------------------------- + * + * TextGetScrollInfoObj -- + * + * This procedure is invoked to parse "yview" scrolling commands for + * text widgets using the new scrolling command syntax ("moveto" or + * "scroll" options). It extends the public Tk_GetScrollInfoObj + * function with the addition of "pixels" as a valid unit alongside + * "pages" and "units". It is a shame the core API isn't more + * flexible in this regard. + * + * Results: + * The return value is either TKTEXT_SCROLL_MOVETO, + * TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_UNITS, TKTEXT_SCROLL_PIXELS or + * TKTEXT_SCROLL_ERROR. This indicates whether the command was + * successfully parsed and what form the command took. If + * TKTEXT_SCROLL_MOVETO, *dblPtr is filled in with the desired + * position; if TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_PIXELS or + * TKTEXT_SCROLL_UNITS, *intPtr is filled in with the number of + * pages/pixels/lines to move (may be negative); if + * TKTEXT_SCROLL_ERROR, the interp's result contains an error + * message. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TextGetScrollInfoObj(interp, textPtr, objc, objv, dblPtr, intPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + TkText *textPtr; /* Information about the text + * widget. */ + int objc; /* # arguments for command. */ + Tcl_Obj *CONST objv[]; /* Arguments for command. */ + double *dblPtr; /* Filled in with argument "moveto" + * option, if any. */ + int *intPtr; /* Filled in with number of pages + * or lines or pixels to scroll, + * if any. */ +{ + char c; + size_t length; + CONST char *arg2; + + arg2 = Tcl_GetStringFromObj(objv[2], &length); + c = arg2[0]; + if ((c == 'm') && (strncmp(arg2, "moveto", length) == 0)) { + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "moveto fraction"); + return TKTEXT_SCROLL_ERROR; + } + if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_MOVETO; + } else if ((c == 's') && (strncmp(arg2, "scroll", length) == 0)) { + CONST char *arg4; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, + "scroll number units|pages|pixels"); + return TKTEXT_SCROLL_ERROR; + } + arg4 = Tcl_GetStringFromObj(objv[4], &length); + c = arg4[0]; + if ((c == 'p') && (length == 1)) { + Tcl_AppendResult(interp, "ambiguous argument \"", arg4, + "\": must be units, pages or pixels", (char *) NULL); + return TKTEXT_SCROLL_ERROR; + } else if ((c == 'p') && (strncmp(arg4, "pages", length) == 0)) { + if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_PAGES; + } else if ((c == 'p') && (strncmp(arg4, "pixels", length) == 0)) { + if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3], + intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_PIXELS; + } else if ((c == 'u') && (strncmp(arg4, "units", length) == 0)) { + if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_UNITS; + } else { + Tcl_AppendResult(interp, "bad argument \"", arg4, + "\": must be units, pages or pixels", (char *) NULL); + return TKTEXT_SCROLL_ERROR; + } + } + Tcl_AppendResult(interp, "unknown option \"", arg2, + "\": must be moveto or scroll", (char *) NULL); + return TKTEXT_SCROLL_ERROR; +} diff --git a/generic/tkTextImage.c b/generic/tkTextImage.c index ef3dd93..35e4138 100644 --- a/generic/tkTextImage.c +++ b/generic/tkTextImage.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextImage.c,v 1.8 2003/09/29 23:15:20 dkf Exp $ + * RCS: @(#) $Id: tkTextImage.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $ */ #include "tk.h" @@ -18,15 +18,6 @@ #include "tkPort.h" /* - * Definitions for alignment values: - */ - -#define ALIGN_BOTTOM 0 -#define ALIGN_CENTER 1 -#define ALIGN_TOP 2 -#define ALIGN_BASELINE 3 - -/* * Macro that determines the size of an embedded image segment: */ @@ -37,12 +28,6 @@ * Prototypes for procedures defined in this file: */ -static int AlignParseProc _ANSI_ARGS_((ClientData clientData, - Tcl_Interp *interp, Tk_Window tkwin, - CONST char *value, char *widgRec, int offset)); -static char * AlignPrintProc _ANSI_ARGS_((ClientData clientData, - Tk_Window tkwin, char *widgRec, int offset, - Tcl_FreeProc **freeProcPtr)); static TkTextSegment * EmbImageCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static void EmbImageCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, @@ -84,30 +69,40 @@ static Tk_SegType tkTextEmbImageType = { }; /* + * Definitions for alignment values: + */ + +static char *alignStrings[] = { + "baseline", "bottom", "center", "top", (char *) NULL +}; + +typedef enum { + ALIGN_BASELINE, ALIGN_BOTTOM, ALIGN_CENTER, ALIGN_TOP +} alignMode; + +/* * Information used for parsing image configuration options: */ -static Tk_CustomOption alignOption = {AlignParseProc, AlignPrintProc, - (ClientData) NULL}; - -static Tk_ConfigSpec configSpecs[] = { - {TK_CONFIG_CUSTOM, "-align", (char *) NULL, (char *) NULL, - "center", 0, TK_CONFIG_DONT_SET_DEFAULT, &alignOption}, - {TK_CONFIG_PIXELS, "-padx", (char *) NULL, (char *) NULL, - "0", Tk_Offset(TkTextEmbImage, padX), - TK_CONFIG_DONT_SET_DEFAULT}, - {TK_CONFIG_PIXELS, "-pady", (char *) NULL, (char *) NULL, - "0", Tk_Offset(TkTextEmbImage, padY), - TK_CONFIG_DONT_SET_DEFAULT}, - {TK_CONFIG_STRING, "-image", (char *) NULL, (char *) NULL, - (char *) NULL, Tk_Offset(TkTextEmbImage, imageString), - TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK}, - {TK_CONFIG_STRING, "-name", (char *) NULL, (char *) NULL, - (char *) NULL, Tk_Offset(TkTextEmbImage, imageName), - TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK}, - {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, - (char *) NULL, 0, 0} +static Tk_OptionSpec optionSpecs[] = { + {TK_OPTION_STRING_TABLE, "-align", (char *) NULL, (char *) NULL, + "center", -1, Tk_Offset(TkTextEmbImage, align), + 0, (ClientData) alignStrings, 0}, + {TK_OPTION_PIXELS, "-padx", (char *) NULL, (char *) NULL, + "0", -1, Tk_Offset(TkTextEmbImage, padX), + 0, 0, 0}, + {TK_OPTION_PIXELS, "-pady", (char *) NULL, (char *) NULL, + "0", -1, Tk_Offset(TkTextEmbImage, padY), + 0, 0, 0}, + {TK_OPTION_STRING, "-image", (char *) NULL, (char *) NULL, + (char *) NULL, -1, Tk_Offset(TkTextEmbImage, imageString), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_STRING, "-name", (char *) NULL, (char *) NULL, + (char *) NULL, -1, Tk_Offset(TkTextEmbImage, imageName), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_END} }; + /* *-------------------------------------------------------------- @@ -155,7 +150,8 @@ TkTextImageCmd(textPtr, interp, objc, objv) return TCL_ERROR; } switch ((enum opts) idx) { - case CMD_CGET: + case CMD_CGET: { + Tcl_Obj *objPtr; if (objc != 5) { Tcl_WrongNumArgs(interp, 3, objv, "index option"); return TCL_ERROR; @@ -169,9 +165,16 @@ TkTextImageCmd(textPtr, interp, objc, objv) Tcl_GetString(objv[3]), "\"", (char *) NULL); return TCL_ERROR; } - return Tk_ConfigureValue(interp, textPtr->tkwin, configSpecs, - (char *) &eiPtr->body.ei, Tcl_GetString(objv[4]), 0); - case CMD_CONF: + objPtr = Tk_GetOptionValue(interp, (char *) &eiPtr->body.ei, + eiPtr->body.ei.optionTable, objv[4], textPtr->tkwin); + if (objPtr == NULL) { + return TCL_ERROR; + } else { + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; + } + } + case CMD_CONF: { if (objc < 4) { Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?"); return TCL_ERROR; @@ -185,16 +188,29 @@ TkTextImageCmd(textPtr, interp, objc, objv) Tcl_GetString(objv[3]), "\"", (char *) NULL); return TCL_ERROR; } - if (objc == 4) { - return Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs, - (char *) &eiPtr->body.ei, (char *) NULL, 0); - } else if (objc == 5) { - return Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs, - (char *) &eiPtr->body.ei, Tcl_GetString(objv[4]), 0); + if (objc <= 5) { + Tcl_Obj* objPtr = Tk_GetOptionInfo(interp, (char *) &eiPtr->body.ei, + eiPtr->body.ei.optionTable, + (objc == 5) ? objv[4] : (Tcl_Obj *) NULL, + textPtr->tkwin); + if (objPtr == NULL) { + return TCL_ERROR; + } else { + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; + } } else { TkTextChanged(textPtr, &index, &index); + /* + * It's probably not true that all window configuration + * can change the line height, so we could be more + * efficient here and only call this when necessary. + */ + TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TK_TEXT_INVALIDATE_ONLY); return EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4); } + } case CMD_CREATE: { int lineIndex; @@ -237,6 +253,7 @@ TkTextImageCmd(textPtr, interp, objc, objv) eiPtr->body.ei.align = ALIGN_CENTER; eiPtr->body.ei.padX = eiPtr->body.ei.padY = 0; eiPtr->body.ei.chunkCount = 0; + eiPtr->body.ei.optionTable = Tk_CreateOptionTable(interp, optionSpecs); /* * Link the segment into the text widget, then configure it (delete @@ -248,10 +265,12 @@ TkTextImageCmd(textPtr, interp, objc, objv) if (EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4) != TCL_OK) { TkTextIndex index2; - TkTextIndexForwChars(&index, 1, &index2); + TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES); TkBTreeDeleteChars(&index, &index2); return TCL_ERROR; } + TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TK_TEXT_INVALIDATE_ONLY); return TCL_OK; } case CMD_NAMES: { @@ -312,16 +331,12 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) int count = 0; /* The counter for picking a unique name */ int conflict = 0; /* True if we have a name conflict */ unsigned int len; /* length of image name */ - CONST char **argv; - argv = TkGetStringsFromObjs(objc, objv); - if (Tk_ConfigureWidget(textPtr->interp, textPtr->tkwin, configSpecs, - objc, argv, (char *) &eiPtr->body.ei,TK_CONFIG_ARGV_ONLY) - != TCL_OK) { - if (argv) ckfree((char *) argv); + if (Tk_SetOptions(textPtr->interp, (char*)&eiPtr->body.ei, + eiPtr->body.ei.optionTable, + objc, objv, textPtr->tkwin, NULL, NULL) != TCL_OK) { return TCL_ERROR; } - if (argv) ckfree((char *) argv); /* * Create the image. Save the old image around and don't free it @@ -331,8 +346,9 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) */ if (eiPtr->body.ei.imageString != NULL) { - image = Tk_GetImage(textPtr->interp, textPtr->tkwin, eiPtr->body.ei.imageString, - EmbImageProc, (ClientData) eiPtr); + image = Tk_GetImage(textPtr->interp, textPtr->tkwin, + eiPtr->body.ei.imageString, EmbImageProc, + (ClientData) eiPtr); if (image == NULL) { return TCL_ERROR; } @@ -350,8 +366,8 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) /* * Find a unique name for this image. Use imageName (or imageString) - * if available, otherwise tack on a #nn and use it. If a name is already - * associated with this image, delete the name. + * if available, otherwise tack on a #nn and use it. If a name is + * already associated with this image, delete the name. */ name = eiPtr->body.ei.imageName; @@ -403,99 +419,6 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) /* *-------------------------------------------------------------- * - * AlignParseProc -- - * - * This procedure is invoked by Tk_ConfigureWidget during - * option processing to handle "-align" options for embedded - * images. - * - * Results: - * A standard Tcl return value. - * - * Side effects: - * The alignment for the embedded image may change. - * - *-------------------------------------------------------------- - */ - - /* ARGSUSED */ -static int -AlignParseProc(clientData, interp, tkwin, value, widgRec, offset) - ClientData clientData; /* Not used.*/ - Tcl_Interp *interp; /* Used for reporting errors. */ - Tk_Window tkwin; /* Window for text widget. */ - CONST char *value; /* Value of option. */ - char *widgRec; /* Pointer to TkTextEmbWindow - * structure. */ - int offset; /* Offset into item (ignored). */ -{ - register TkTextEmbImage *embPtr = (TkTextEmbImage *) widgRec; - - if (strcmp(value, "baseline") == 0) { - embPtr->align = ALIGN_BASELINE; - } else if (strcmp(value, "bottom") == 0) { - embPtr->align = ALIGN_BOTTOM; - } else if (strcmp(value, "center") == 0) { - embPtr->align = ALIGN_CENTER; - } else if (strcmp(value, "top") == 0) { - embPtr->align = ALIGN_TOP; - } else { - Tcl_AppendResult(interp, "bad alignment \"", value, - "\": must be baseline, bottom, center, or top", - (char *) NULL); - return TCL_ERROR; - } - return TCL_OK; -} - -/* - *-------------------------------------------------------------- - * - * AlignPrintProc -- - * - * This procedure is invoked by the Tk configuration code - * to produce a printable string for the "-align" configuration - * option for embedded images. - * - * Results: - * The return value is a string describing the embedded - * images's current alignment. - * - * Side effects: - * None. - * - *-------------------------------------------------------------- - */ - - /* ARGSUSED */ -static char * -AlignPrintProc(clientData, tkwin, widgRec, offset, freeProcPtr) - ClientData clientData; /* Ignored. */ - Tk_Window tkwin; /* Window for text widget. */ - char *widgRec; /* Pointer to TkTextEmbImage - * structure. */ - int offset; /* Ignored. */ - Tcl_FreeProc **freeProcPtr; /* Pointer to variable to fill in with - * information about how to reclaim - * storage for return string. */ -{ - switch (((TkTextEmbImage *) widgRec)->align) { - case ALIGN_BASELINE: - return "baseline"; - case ALIGN_BOTTOM: - return "bottom"; - case ALIGN_CENTER: - return "center"; - case ALIGN_TOP: - return "top"; - default: - return "??"; - } -} - -/* - *-------------------------------------------------------------- - * * EmbImageDeleteProc -- * * This procedure is invoked by the text B-tree code whenever @@ -536,11 +459,8 @@ EmbImageDeleteProc(eiPtr, linePtr, treeGone) } Tk_FreeImage(eiPtr->body.ei.image); } - Tk_FreeOptions(configSpecs, (char *) &eiPtr->body.ei, - eiPtr->body.ei.textPtr->display, 0); - if (eiPtr->body.ei.name != NULL) { - ckfree(eiPtr->body.ei.name); - } + Tk_FreeConfigOptions((char *) &eiPtr->body.ei, eiPtr->body.ei.optionTable, + eiPtr->body.ei.textPtr->tkwin); ckfree((char *) eiPtr); return 0; } @@ -896,4 +816,11 @@ EmbImageProc(clientData, x, y, width, height, imgWidth, imgHeight) index.linePtr = eiPtr->body.ei.linePtr; index.byteIndex = TkTextSegToOffset(eiPtr, eiPtr->body.ei.linePtr); TkTextChanged(eiPtr->body.ei.textPtr, &index, &index); + /* + * It's probably not true that all image changes + * can change the line height, so we could be more + * efficient here and only call this when necessary. + */ + TkTextInvalidateLineMetrics(eiPtr->body.ei.textPtr, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } diff --git a/generic/tkTextIndex.c b/generic/tkTextIndex.c index 06396db..66caa5a 100644 --- a/generic/tkTextIndex.c +++ b/generic/tkTextIndex.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextIndex.c,v 1.8 2003/10/10 15:56:22 dkf Exp $ + * RCS: @(#) $Id: tkTextIndex.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $ */ #include "default.h" @@ -25,18 +25,28 @@ #define LAST_CHAR 1000000 /* + * Modifiers for index parsing: 'display', 'any' or nothing. + */ +#define TKINDEX_NONE 0 +#define TKINDEX_DISPLAY 1 +#define TKINDEX_ANY 2 + +/* * Forward declarations for procedures defined later in this file: */ -static CONST char * ForwBack _ANSI_ARGS_((CONST char *string, - TkTextIndex *indexPtr)); -static CONST char * StartEnd _ANSI_ARGS_((CONST char *string, - TkTextIndex *indexPtr)); +static CONST char * ForwBack _ANSI_ARGS_((TkText *textPtr, + CONST char *string, TkTextIndex *indexPtr)); +static CONST char * StartEnd _ANSI_ARGS_((TkText *textPtr, + CONST char *string, TkTextIndex *indexPtr)); static int GetIndex _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, CONST char *string, TkTextIndex *indexPtr, int *canCachePtr)); - +/* + * The "textindex" Tcl_Obj definition: + */ + static void DupTextIndexInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr, Tcl_Obj *copyPtr)); static void FreeTextIndexInternalRep _ANSI_ARGS_((Tcl_Obj *listPtr)); @@ -96,7 +106,6 @@ DupTextIndexInternalRep(srcPtr, copyPtr) SET_TEXTINDEX(copyPtr, dupIndexPtr); SET_INDEXEPOCH(copyPtr, epoch); } - /* * This will not be called except by TkTextNewIndexObj below. * This is because if a TkTextIndex is no longer valid, it is @@ -117,7 +126,6 @@ UpdateStringOfTextIndex(objPtr) strcpy(objPtr->bytes, buffer); objPtr->length = len; } - static int SetTextIndexFromAny(interp, objPtr) Tcl_Interp *interp; /* Used for error reporting if not NULL. */ @@ -128,7 +136,7 @@ SetTextIndexFromAny(interp, objPtr) -1); return TCL_ERROR; } - + /* *--------------------------------------------------------------------------- * @@ -176,7 +184,7 @@ MakeObjIndex(textPtr, objPtr, origPtr) } return indexPtr; } - + CONST TkTextIndex* TkTextGetIndexFromObj(interp, textPtr, objPtr) Tcl_Interp *interp; /* Use this for error reporting. */ @@ -274,6 +282,62 @@ TkTextNewIndexObj(textPtr, indexPtr) /* *--------------------------------------------------------------------------- * + * TkTextMakePixelIndex -- + * + * Given a pixel index and a byte index, look things up in the B-tree + * and fill in a TkTextIndex structure. + * + * Results: + * + * The structure at *indexPtr is filled in with information about + * the character at pixelIndex (or the closest existing character, + * if the specified one doesn't exist), and the number of excess + * pixels is returned as a result. This means if the given pixel + * index is exactly correct for the top-edge of the indexPtr, then + * zero will be returned, and otherwise we will return the + * calculation 'desired pixelIndex' - 'actual pixel index of + * indexPtr'. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextMakePixelIndex(textPtr, pixelIndex, indexPtr) + TkText* textPtr; /* The Text Widget */ + int pixelIndex; /* pixel-index of desired line (0 means first + * pixel of first line of text). */ + TkTextIndex *indexPtr; /* Structure to fill in. */ +{ + int pixelOffset = 0; + + indexPtr->tree = textPtr->tree; + indexPtr->textPtr = textPtr; + + if (pixelIndex < 0) { + pixelIndex = 0; + } + indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->tree, pixelIndex, + &pixelOffset); + if (indexPtr->linePtr == NULL) { + indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->tree, + TkBTreeNumPixels(textPtr->tree)-1, &pixelOffset); + indexPtr->byteIndex = 0; + return pixelOffset; + } + indexPtr->byteIndex = 0; + + if (pixelOffset <= 0) { + return 0; + } + return TkTextMeasureDown(textPtr, indexPtr, pixelOffset); +} + +/* + *--------------------------------------------------------------------------- + * * TkTextMakeByteIndex -- * * Given a line index and a byte index, look things up in the B-tree @@ -293,7 +357,7 @@ TkTextNewIndexObj(textPtr, indexPtr) TkTextIndex * TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) - TkTextBTree tree; /* Tree that lineIndex and charIndex refer + TkTextBTree tree; /* Tree that lineIndex and byteIndex refer * to. */ int lineIndex; /* Index of desired line (0 means first * line of text). */ @@ -827,9 +891,9 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) } if ((*cp == '+') || (*cp == '-')) { - cp = ForwBack(cp, indexPtr); + cp = ForwBack(textPtr, cp, indexPtr); } else { - cp = StartEnd(cp, indexPtr); + cp = StartEnd(textPtr, cp, indexPtr); } if (cp == NULL) { goto error; @@ -965,7 +1029,8 @@ TkTextIndexCmp(index1Ptr, index2Ptr) */ static CONST char * -ForwBack(string, indexPtr) +ForwBack(textPtr, string, indexPtr) + TkText *textPtr; /* Information about text widget. */ CONST char *string; /* String to parse for additional info * about modifier (count and units). * Points to "+" or "-" that starts @@ -974,7 +1039,7 @@ ForwBack(string, indexPtr) { register CONST char *p, *units; char *end; - int count, lineIndex; + int count, lineIndex, modifier; size_t length; /* @@ -996,8 +1061,12 @@ ForwBack(string, indexPtr) /* * Find the end of this modifier (next space or + or - character), - * then parse the unit specifier and update the position - * accordingly. + * then check if there is a textual 'display' or 'any' modifier. + * These modifiers can be their own word (in which case they can + * be abbreviated) or they can follow on to the actual unit in + * a single word (in which case no abbreviation is allowed). So, + * 'display lines', 'd lines', 'displaylin' are all ok, but 'dline' + * is not. */ units = p; @@ -1005,43 +1074,166 @@ ForwBack(string, indexPtr) p++; } length = p - units; + if ((*units == 'd') && (strncmp(units, "display", + (length > 7 ? 7 : length)) == 0)) { + modifier = TKINDEX_DISPLAY; + if (length > 7) { + p -= (length - 7); + } + } else if ((*units == 'a') && (strncmp(units, "any", + (length > 3 ? 3 : length)) == 0)) { + modifier = TKINDEX_ANY; + if (length > 3) { + p -= (length - 3); + } + } else { + modifier = TKINDEX_NONE; + } + + /* + * If we had a modifier, which we interpreted ok, so now forward + * to the actual units. + */ + if (modifier != TKINDEX_NONE) { + while (isspace(UCHAR(*p))) { + p++; + } + units = p; + while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) { + p++; + } + length = p - units; + } + + /* + * Finally parse the units. + */ if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) { + TkTextCountType type; + if (modifier == TKINDEX_NONE) { + type = COUNT_INDICES; + } else if (modifier == TKINDEX_ANY) { + type = COUNT_CHARS; + } else { + type = COUNT_DISPLAY_CHARS; + } if (*string == '+') { - TkTextIndexForwChars(indexPtr, count, indexPtr); + TkTextIndexForwChars(indexPtr, count, indexPtr, type); } else { - TkTextIndexBackChars(indexPtr, count, indexPtr); + TkTextIndexBackChars(indexPtr, count, indexPtr, type); + } + } else if ((*units == 'i') && (strncmp(units, "indices", length) == 0)) { + TkTextCountType type; + if (modifier == TKINDEX_DISPLAY) { + type = COUNT_DISPLAY_INDICES; + } else { + type = COUNT_INDICES; } - } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { - lineIndex = TkBTreeLineIndex(indexPtr->linePtr); if (*string == '+') { - lineIndex += count; + TkTextIndexForwChars(indexPtr, count, indexPtr, type); + } else { + TkTextIndexBackChars(indexPtr, count, indexPtr, type); + } + } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { + if (modifier == TKINDEX_DISPLAY) { + /* + * Find the appropriate pixel offset of the current position + * within its display line. This also has the side-effect of + * moving indexPtr, but that doesn't matter since we will do + * it again below. + * + * Then find the right display line, and finally calculated + * the index we want in that display line, based on the + * original pixel offset. + */ + int xOffset, forward; + if (TkTextIsElided(textPtr, indexPtr)) { + /* Go forward to the first non-elided index */ + TkTextIndexForwChars(indexPtr, 0, indexPtr, + COUNT_DISPLAY_INDICES | COUNT_IS_ELIDED); + } + /* + * Unlike the Forw/BackChars code, the display line code + * is sensitive to whether we are genuinely going + * forwards or backwards. So, we need to determine that. + * This is important in the case where we have "+ -3 + * displaylines", for example. + */ + if ((count < 0) ^ (*string == '-')) { + forward = 0; + } else { + forward = 1; + } + count = abs(count); + if (count == 0) { + return p; + } + if (forward) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, &xOffset); + while (count-- > 0) { + /* + * Go to the end of the line, then forward one + * char/byte to get to the beginning of the next + * line. + */ + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL); + TkTextIndexForwChars(indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } + } else { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, &xOffset); + while (count-- > 0) { + /* + * Go to the beginning of the line, then backward one + * char/byte to get to the end of the previous line + */ + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + TkTextIndexBackChars(indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + } + /* + * This call assumes indexPtr is the beginning of a display line + * and moves it to the 'xOffset' position of that line, which is + * just what we want. + */ + TkTextIndexOfX(textPtr, xOffset, indexPtr); } else { - lineIndex -= count; + lineIndex = TkBTreeLineIndex(indexPtr->linePtr); + if (*string == '+') { + lineIndex += count; + } else { + lineIndex -= count; + + /* + * The check below retains the character position, even + * if the line runs off the start of the file. Without + * it, the character position will get reset to 0 by + * TkTextMakeIndex. + */ + if (lineIndex < 0) { + lineIndex = 0; + } + } /* - * The check below retains the character position, even - * if the line runs off the start of the file. Without - * it, the character position will get reset to 0 by - * TkTextMakeIndex. + * This doesn't work quite right if using a proportional font or + * UTF-8 characters with varying numbers of bytes, or if there + * are embedded windows, images, etc. The cursor will bop + * around, keeping a constant number of bytes (not characters) + * from the left edge (but making sure not to split any UTF-8 + * characters), regardless of the x-position the index + * corresponds to. The proper way to do this is to get the + * x-position of the index and then pick the character at the + * same x-position in the new line. */ - if (lineIndex < 0) { - lineIndex = 0; - } + TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex, + indexPtr); } - /* - * This doesn't work quite right if using a proportional font or - * UTF-8 characters with varying numbers of bytes. The cursor will - * bop around, keeping a constant number of bytes (not characters) - * from the left edge (but making sure not to split any UTF-8 - * characters), regardless of the x-position the index corresponds - * to. The proper way to do this is to get the x-position of the - * index and then pick the character at the same x-position in the - * new line. - */ - - TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex, - indexPtr); } else { return NULL; } @@ -1060,6 +1252,9 @@ ForwBack(string, indexPtr) * *dstPtr is modified to refer to the character "count" bytes after * srcPtr, or to the last character in the TkText if there aren't * "count" bytes left. + * + * In this latter case, the function returns '1' to indicate + * that not all of 'byteCount' could be used. * * Side effects: * None. @@ -1067,7 +1262,7 @@ ForwBack(string, indexPtr) *--------------------------------------------------------------------------- */ -void +int TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) CONST TkTextIndex *srcPtr; /* Source index. */ int byteCount; /* How many bytes forward to move. May be @@ -1080,7 +1275,7 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) if (byteCount < 0) { TkTextIndexBackBytes(srcPtr, -byteCount, dstPtr); - return; + return 0; } *dstPtr = *srcPtr; @@ -1102,13 +1297,13 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) */ if (dstPtr->byteIndex < lineLength) { - return; + return 0; } dstPtr->byteIndex -= lineLength; linePtr = TkBTreeNextLine(dstPtr->linePtr); if (linePtr == NULL) { dstPtr->byteIndex = lineLength - 1; - return; + return 1; } dstPtr->linePtr = linePtr; } @@ -1120,12 +1315,18 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) * TkTextIndexForwChars -- * * Given an index for a text widget, this procedure creates a new - * index that points "count" characters ahead of the source index. + * index that points "count" items of type given by "type" ahead of + * the source index. "count" can be zero, which is useful in + * the case where one wishes to move forward by display + * (non-elided) chars or indices or one wishes to move forward + * by chars, skipping any intervening indices. In this case + * dstPtr will point to the first acceptable index which is + * encountered. * * Results: - * *dstPtr is modified to refer to the character "count" characters + * *dstPtr is modified to refer to the character "count" items * after srcPtr, or to the last character in the TkText if there - * aren't "count" characters left in the file. + * aren't sufficient items left in the widget. * * Side effects: * None. @@ -1134,22 +1335,28 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) */ void -TkTextIndexForwChars(srcPtr, charCount, dstPtr) +TkTextIndexForwChars(srcPtr, charCount, dstPtr, type) CONST TkTextIndex *srcPtr; /* Source index. */ int charCount; /* How many characters forward to move. * May be negative. */ TkTextIndex *dstPtr; /* Destination index: gets modified. */ + TkTextCountType type; /* The type of item to count */ { TkTextLine *linePtr; TkTextSegment *segPtr; int byteOffset; char *start, *end, *p; Tcl_UniChar ch; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); if (charCount < 0) { - TkTextIndexBackChars(srcPtr, -charCount, dstPtr); + TkTextIndexBackChars(srcPtr, -charCount, dstPtr, type); return; } + if (checkElided) { + elide = ((type & COUNT_IS_ELIDED) ? 1 : 0); + } *dstPtr = *srcPtr; @@ -1166,23 +1373,49 @@ TkTextIndexForwChars(srcPtr, charCount, dstPtr) */ for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { - if (segPtr->typePtr == &tkTextCharType) { - start = segPtr->body.chars + byteOffset; - end = segPtr->body.chars + segPtr->size; - for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) { - if (charCount == 0) { - dstPtr->byteIndex += (p - start); - return; + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current + * segment isn't visible, then we simply continue the + * loop + */ + if (checkElided) { + if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + if (segPtr->body.toggle.tagPtr->elideString != NULL) { + if (elide) { + elide = (segPtr->typePtr == &tkTextToggleOffType) + & !segPtr->body.toggle.tagPtr->elide; + } else { + elide = (segPtr->typePtr == &tkTextToggleOnType) + & segPtr->body.toggle.tagPtr->elide; + } } - charCount--; } - } else { - if (charCount < segPtr->size - byteOffset) { - dstPtr->byteIndex += charCount; - return; + } + + if (!elide) { + if (segPtr->typePtr == &tkTextCharType) { + start = segPtr->body.chars + byteOffset; + end = segPtr->body.chars + segPtr->size; + for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) { + if (charCount == 0) { + dstPtr->byteIndex += (p - start); + return; + } + charCount--; + } + } else { + if (type & COUNT_INDICES) { + if (charCount < segPtr->size - byteOffset) { + dstPtr->byteIndex += charCount; + return; + } + charCount -= segPtr->size - byteOffset; + } } - charCount -= segPtr->size - byteOffset; } + dstPtr->byteIndex += segPtr->size - byteOffset; byteOffset = 0; } @@ -1207,6 +1440,155 @@ TkTextIndexForwChars(srcPtr, charCount, dstPtr) /* *--------------------------------------------------------------------------- * + * TkTextIndexCount -- + * + * Given an ordered pair of indices in a text widget, this + * procedure counts how many characters (not bytes) are between + * the two indices. + * + * It is illegal to call this procedure with unordered indices. + * + * Note that 'textPtr' is only used if we need to check for + * elided attributes, i.e. if type is COUNT_DISPLAY_INDICES or + * COUNT_DISPLAY_CHARS. + * + * Results: + * The number of characters in the given range, which meet + * the appropriate 'type' attributes. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type) + CONST TkText *textPtr; /* Overall information about text widget. */ + CONST TkTextIndex *indexPtr1;/* Index describing location of + * character from which to count. */ + CONST TkTextIndex *indexPtr2;/* Index describing location of last + * character at which to stop the + * count. */ + TkTextCountType type; /* The kind of indices to count */ +{ + TkTextLine *linePtr1; + TkTextSegment *segPtr, *seg2Ptr = NULL; + int byteOffset, maxBytes; + int count = 0; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); + + /* + * Find seg that contains src index, and remember + * how many bytes not to count in the given segment. + */ + + segPtr = TkTextIndexToSeg(indexPtr1, &byteOffset); + linePtr1 = indexPtr1->linePtr; + + seg2Ptr = TkTextIndexToSeg(indexPtr2, &maxBytes); + + if (checkElided) { + elide = TkTextIsElided(textPtr, indexPtr1); + } + + while (1) { + /* + * Go through each segment in line adding up the number + * of characters. + */ + + for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current + * segment isn't visible, then we simply continue the + * loop + */ + if (checkElided) { + if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + if (segPtr->body.toggle.tagPtr->elideString != NULL) { + if (elide) { + elide = (segPtr->typePtr == &tkTextToggleOffType) + & !segPtr->body.toggle.tagPtr->elide; + } else { + elide = (segPtr->typePtr == &tkTextToggleOnType) + & segPtr->body.toggle.tagPtr->elide; + } + } + } + if (elide) { + if (segPtr == seg2Ptr) { + return count; + } + byteOffset = 0; + continue; + } + } + + if (segPtr->typePtr == &tkTextCharType) { + int byteLen = segPtr->size - byteOffset; + register unsigned char *str = + (unsigned char *) segPtr->body.chars + byteOffset; + register int i; + + if (segPtr == seg2Ptr) { + if (byteLen > (maxBytes - byteOffset)) { + byteLen = maxBytes - byteOffset; + } + } + i = byteLen; + + /* + * This is a speed sensitive function, so run specially over the + * string to count continuous ascii characters before resorting + * to the Tcl_NumUtfChars call. This is a long form of: + * stringPtr->numChars = + * Tcl_NumUtfChars(objPtr->bytes, objPtr->length); + */ + + while (i && (*str < 0xC0)) { i--; str++; } + count += byteLen - i; + if (i) { + count += Tcl_NumUtfChars(segPtr->body.chars + byteOffset + + (byteLen - i), i); + } + } else { + if (type & COUNT_INDICES) { + int byteLen = segPtr->size - byteOffset; + if (segPtr == seg2Ptr) { + if (byteLen > (maxBytes - byteOffset)) { + byteLen = maxBytes - byteOffset; + } + } + count += byteLen; + } + } + if (segPtr == seg2Ptr) { + return count; + } + byteOffset = 0; + } + + /* + * Go to the next line. If we are at the end of the text item, + * back up one byte (for the terminal '\n' character) and return + * that index. + */ + + linePtr1 = TkBTreeNextLine(linePtr1); + if (linePtr1 == NULL) { + panic("Reached end of text widget when counting characters"); + } + segPtr = linePtr1->segPtr; + } +} + +/* + *--------------------------------------------------------------------------- + * * TkTextIndexBackBytes -- * * Given an index for a text widget, this procedure creates a new @@ -1274,12 +1656,18 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) * TkTextIndexBackChars -- * * Given an index for a text widget, this procedure creates a new - * index that points "count" characters earlier than the source index. + * index that points "count" items of type given by "type" earlier + * than the source index. "count" can be zero, which is useful in + * the case where one wishes to move backward by display + * (non-elided) chars or indices or one wishes to move backward by + * chars, skipping any intervening indices. In this case the + * returned index *dstPtr will point just _after_ the first + * acceptable index which is encountered. * * Results: - * *dstPtr is modified to refer to the character "count" characters - * before srcPtr, or to the first character in the file if there - * aren't "count" characters earlier than srcPtr. + * *dstPtr is modified to refer to the character "count" items + * before srcPtr, or to the first index in the window if there + * aren't sufficient items earlier than srcPtr. * * Side effects: * None. @@ -1288,21 +1676,27 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) */ void -TkTextIndexBackChars(srcPtr, charCount, dstPtr) +TkTextIndexBackChars(srcPtr, charCount, dstPtr, type) CONST TkTextIndex *srcPtr; /* Source index. */ int charCount; /* How many characters backward to move. * May be negative. */ TkTextIndex *dstPtr; /* Destination index: gets modified. */ + TkTextCountType type; /* The type of item to count */ { TkTextSegment *segPtr, *oldPtr; int lineIndex, segSize; CONST char *p, *start, *end; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); - if (charCount <= 0) { - TkTextIndexForwChars(srcPtr, -charCount, dstPtr); + if (charCount < 0) { + TkTextIndexForwChars(srcPtr, -charCount, dstPtr, type); return; } - + if (checkElided) { + elide = ((type & COUNT_IS_ELIDED) ? 1 : 0); + } + *dstPtr = *srcPtr; /* @@ -1320,25 +1714,50 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr) segSize -= segPtr->size; } while (1) { - if (segPtr->typePtr == &tkTextCharType) { - start = segPtr->body.chars; - end = segPtr->body.chars + segSize; - for (p = end; ; p = Tcl_UtfPrev(p, start)) { - if (charCount == 0) { - dstPtr->byteIndex -= (end - p); - return; - } - if (p == start) { - break; + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current + * segment isn't visible, then we simply continue the + * loop + */ + if (checkElided) { + if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + if (segPtr->body.toggle.tagPtr->elideString != NULL) { + if (elide) { + elide = (segPtr->typePtr == &tkTextToggleOnType) + & !segPtr->body.toggle.tagPtr->elide; + } else { + elide = (segPtr->typePtr == &tkTextToggleOffType) + & segPtr->body.toggle.tagPtr->elide; + } } - charCount--; } - } else { - if (charCount <= segSize) { - dstPtr->byteIndex -= charCount; - return; + } + + if (!elide) { + if (segPtr->typePtr == &tkTextCharType) { + start = segPtr->body.chars; + end = segPtr->body.chars + segSize; + for (p = end; ; p = Tcl_UtfPrev(p, start)) { + if (charCount == 0) { + dstPtr->byteIndex -= (end - p); + return; + } + if (p == start) { + break; + } + charCount--; + } + } else { + if (type & COUNT_INDICES) { + if (charCount <= segSize) { + dstPtr->byteIndex -= charCount; + return; + } + charCount -= segSize; + } } - charCount -= segSize; } dstPtr->byteIndex -= segSize; @@ -1405,18 +1824,20 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr) */ static CONST char * -StartEnd(string, indexPtr) +StartEnd(textPtr, string, indexPtr) + TkText *textPtr; /* Information about text widget. */ CONST char *string; /* String to parse for additional info * about modifier (count and units). * Points to first character of modifer * word. */ - TkTextIndex *indexPtr; /* Index to mdoify based on string. */ + TkTextIndex *indexPtr; /* Index to modify based on string. */ { CONST char *p; int c, offset; size_t length; register TkTextSegment *segPtr; - + int modifier; + /* * Find the end of the modifier word. */ @@ -1425,17 +1846,57 @@ StartEnd(string, indexPtr) /* Empty loop body. */ } length = p-string; + if ((*string == 'd') && (strncmp(string, "display", + (length > 7 ? 7 : length)) == 0)) { + modifier = TKINDEX_DISPLAY; + if (length > 7) { + p -= (length - 7); + } + } else if ((*string == 'a') && (strncmp(string, "any", + (length > 3 ? 3 : length)) == 0)) { + modifier = TKINDEX_ANY; + if (length > 3) { + p -= (length - 3); + } + } else { + modifier = TKINDEX_NONE; + } + + /* + * If we had a modifier, which we interpreted ok, so now forward + * to the actual units. + */ + if (modifier != TKINDEX_NONE) { + while (isspace(UCHAR(*p))) { + p++; + } + string = p; + while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) { + p++; + } + length = p - string; + } + if ((*string == 'l') && (strncmp(string, "lineend", length) == 0) && (length >= 5)) { - indexPtr->byteIndex = 0; - for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; - segPtr = segPtr->nextPtr) { - indexPtr->byteIndex += segPtr->size; + if (modifier == TKINDEX_DISPLAY) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL); + } else { + indexPtr->byteIndex = 0; + for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; + segPtr = segPtr->nextPtr) { + indexPtr->byteIndex += segPtr->size; + } + /* We know '\n' is encoded with a single byte index */ + indexPtr->byteIndex -= sizeof(char); } - indexPtr->byteIndex -= sizeof(char); } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0) && (length >= 5)) { - indexPtr->byteIndex = 0; + if (modifier == TKINDEX_DISPLAY) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + } else { + indexPtr->byteIndex = 0; + } } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0) && (length >= 5)) { int firstChar = 1; @@ -1446,6 +1907,10 @@ StartEnd(string, indexPtr) * a character that isn't part of a word and stop there. */ + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } segPtr = TkTextIndexToSeg(indexPtr, &offset); while (1) { if (segPtr->typePtr == &tkTextCharType) { @@ -1462,12 +1927,21 @@ StartEnd(string, indexPtr) } } if (firstChar) { - TkTextIndexForwChars(indexPtr, 1, indexPtr); + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } else { + TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_INDICES); + } } } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0) && (length >= 5)) { int firstChar = 1; + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } /* * Starting with the current character, look for one that's not * part of a word and keep moving backward until you find one. @@ -1495,7 +1969,12 @@ StartEnd(string, indexPtr) } } if (!firstChar) { - TkTextIndexForwChars(indexPtr, 1, indexPtr); + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } else { + TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_INDICES); + } } } else { return NULL; diff --git a/generic/tkTextMark.c b/generic/tkTextMark.c index d48e10a..7fcb275 100644 --- a/generic/tkTextMark.c +++ b/generic/tkTextMark.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextMark.c,v 1.8 2003/05/19 14:37:20 dkf Exp $ + * RCS: @(#) $Id: tkTextMark.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $ */ #include "tkInt.h" @@ -253,7 +253,7 @@ TkTextMarkCmd(textPtr, interp, objc, objv) TkTextSegment * TkTextSetMark(textPtr, name, indexPtr) TkText *textPtr; /* Text widget in which to create mark. */ - CONST char *name; /* Name of mark to set. */ + CONST char *name; /* Name of mark to set. */ TkTextIndex *indexPtr; /* Where to set mark. */ { Tcl_HashEntry *hPtr; @@ -274,11 +274,15 @@ TkTextSetMark(textPtr, name, indexPtr) if (markPtr == textPtr->insertMarkPtr) { TkTextIndex index, index2; TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - TkTextIndexForwChars(&index, 1, &index2); + TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES); + /* + * While we wish to redisplay, no heights have changed, so + * no need to call TkTextInvalidateLineMetrics. + */ TkTextChanged(textPtr, &index, &index2); if (TkBTreeLineIndex(indexPtr->linePtr) == TkBTreeNumLines(textPtr->tree)) { - TkTextIndexBackChars(indexPtr, 1, &insertIndex); + TkTextIndexBackChars(indexPtr, 1, &insertIndex, COUNT_INDICES); indexPtr = &insertIndex; } } @@ -303,7 +307,11 @@ TkTextSetMark(textPtr, name, indexPtr) if (markPtr == textPtr->insertMarkPtr) { TkTextIndex index2; - TkTextIndexForwChars(indexPtr, 1, &index2); + TkTextIndexForwChars(indexPtr, 1, &index2, COUNT_INDICES); + /* + * While we wish to redisplay, no heights have changed, so + * no need to call TkTextInvalidateLineMetrics + */ TkTextChanged(textPtr, indexPtr, &index2); } return markPtr; @@ -540,9 +548,20 @@ TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) * corresponds to y. */ { TkText *textPtr = (TkText *) chunkPtr->clientData; + TkTextIndex index; int halfWidth = textPtr->insertWidth/2; - - if ((x + halfWidth) < 0) { + int rightSideWidth; + int ix = 0, iy = 0, iw = 0, ih = 0, charWidth = 0; + + if(textPtr->insertCursorType) { + TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); + TkTextCharBbox(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. @@ -564,11 +583,11 @@ TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) if (textPtr->flags & INSERT_ON) { Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, - x - halfWidth, y, textPtr->insertWidth, height, + 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, textPtr->insertWidth, height, + x - halfWidth, y, charWidth + textPtr->insertWidth, height, 0, TK_RELIEF_FLAT); } } diff --git a/generic/tkTextTag.c b/generic/tkTextTag.c index e3bf289..00a9193 100644 --- a/generic/tkTextTag.c +++ b/generic/tkTextTag.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextTag.c,v 1.10 2003/05/27 15:35:53 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextTag.c,v 1.11 2003/10/31 09:02:12 vincentdarley Exp $ */ #include "default.h" @@ -162,7 +162,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) } tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3])); for (i = 4; i < objc; i += 2) { - if (TkTextGetObjIndex(interp, textPtr, objv[i], &index1) != TCL_OK) { + if (TkTextGetObjIndex(interp, textPtr, objv[i], + &index1) != TCL_OK) { return TCL_ERROR; } if (objc > (i+1)) { @@ -175,7 +176,7 @@ TkTextTagCmd(textPtr, interp, objc, objv) } } else { index2 = index1; - TkTextIndexForwChars(&index2, 1, &index2); + TkTextIndexForwChars(&index2, 1, &index2, COUNT_INDICES); } if (tagPtr->affectsDisplay) { @@ -188,38 +189,40 @@ TkTextTagCmd(textPtr, interp, objc, objv) TkTextEventuallyRepick(textPtr); } - TkBTreeTag(&index1, &index2, tagPtr, addTag); - - /* - * If the tag is "sel" then grab the selection if we're supposed - * to export it and don't already have it. Also, invalidate - * partially-completed selection retrievals. - */ - - if (tagPtr == textPtr->selTagPtr) { - XEvent event; + if (TkBTreeTag(&index1, &index2, tagPtr, addTag)) { /* - * Send an event that the selection changed. - * This is equivalent to - * "event generate $textWidget <<Selection>>" + * If the tag is "sel", and we actually adjusted + * something then grab the selection if we're + * supposed to export it and don't already have it. + * Also, invalidate partially-completed selection + * retrievals. */ - memset((VOID *) &event, 0, sizeof(event)); - event.xany.type = VirtualEvent; - event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); - event.xany.send_event = False; - event.xany.window = Tk_WindowId(textPtr->tkwin); - event.xany.display = Tk_Display(textPtr->tkwin); - ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); - Tk_HandleEvent(&event); - - if (addTag && textPtr->exportSelection - && !(textPtr->flags & GOT_SELECTION)) { - Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, - TkTextLostSelection, (ClientData) textPtr); - textPtr->flags |= GOT_SELECTION; + if (tagPtr == textPtr->selTagPtr) { + XEvent event; + /* + * Send an event that the selection changed. + * This is equivalent to + * "event generate $textWidget <<Selection>>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); + Tk_HandleEvent(&event); + + if (addTag && textPtr->exportSelection + && !(textPtr->flags & GOT_SELECTION)) { + Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, + TkTextLostSelection, (ClientData) textPtr); + textPtr->flags |= GOT_SELECTION; + } + textPtr->abortSelections = 1; } - textPtr->abortSelections = 1; } } break; @@ -464,26 +467,37 @@ TkTextTagCmd(textPtr, interp, objc, objv) textPtr->selFgColorPtr = tagPtr->fgColor; } tagPtr->affectsDisplay = 0; - if ((tagPtr->border != NULL) - || (tagPtr->reliefString != NULL) - || (tagPtr->bgStipple != None) - || (tagPtr->fgColor != NULL) || (tagPtr->tkfont != None) - || (tagPtr->fgStipple != None) + tagPtr->affectsDisplayGeometry = 0; + if ((tagPtr->elideString != NULL) + || (tagPtr->tkfont != None) || (tagPtr->justifyString != NULL) || (tagPtr->lMargin1String != NULL) || (tagPtr->lMargin2String != NULL) || (tagPtr->offsetString != NULL) - || (tagPtr->overstrikeString != NULL) || (tagPtr->rMarginString != NULL) || (tagPtr->spacing1String != NULL) || (tagPtr->spacing2String != NULL) || (tagPtr->spacing3String != NULL) || (tagPtr->tabStringPtr != NULL) - || (tagPtr->underlineString != NULL) - || (tagPtr->elideString != NULL) || (tagPtr->wrapMode != TEXT_WRAPMODE_NULL)) { tagPtr->affectsDisplay = 1; + tagPtr->affectsDisplayGeometry = 1; + } + if ((tagPtr->border != NULL) + || (tagPtr->reliefString != NULL) + || (tagPtr->bgStipple != None) + || (tagPtr->fgColor != NULL) + || (tagPtr->fgStipple != None) + || (tagPtr->overstrikeString != NULL) + || (tagPtr->underlineString != NULL)) { + tagPtr->affectsDisplay = 1; } + /* + * This line is totally unnecessary if this is a new + * tag, since it can't possibly have been applied to + * anything yet. We might wish to test for that + * case specially + */ TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL, tagPtr, 1); return result; @@ -582,7 +596,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) case TAG_NAMES: { TkTextTag **arrayPtr; int arraySize; - + Tcl_Obj *listObj; + if ((objc != 3) && (objc != 4)) { Tcl_WrongNumArgs(interp, 3, objv, "?index?"); return TCL_ERROR; @@ -609,10 +624,13 @@ TkTextTagCmd(textPtr, interp, objc, objv) } } SortTags(arraySize, arrayPtr); + listObj = Tcl_NewListObj(0, NULL); for (i = 0; i < arraySize; i++) { tagPtr = arrayPtr[i]; - Tcl_AppendElement(interp, tagPtr->name); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewStringObj(tagPtr->name,-1)); } + Tcl_SetObjResult(interp, listObj); ckfree((char *) arrayPtr); break; } @@ -778,7 +796,7 @@ TkTextTagCmd(textPtr, interp, objc, objv) } case TAG_RANGES: { TkTextSearch tSearch; - char position[TK_POS_CHARS]; + Tcl_Obj *listObj = Tcl_NewListObj(0, NULL); if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "tagName"); @@ -793,13 +811,14 @@ TkTextTagCmd(textPtr, interp, objc, objv) 0, &last); TkBTreeStartSearch(&first, &last, tagPtr, &tSearch); if (TkBTreeCharTagged(&first, tagPtr)) { - TkTextPrintIndex(&first, position); - Tcl_AppendElement(interp, position); + Tcl_ListObjAppendElement(interp, listObj, + TkTextNewIndexObj(textPtr, &first)); } while (TkBTreeNextTag(&tSearch)) { - TkTextPrintIndex(&tSearch.curIndex, position); - Tcl_AppendElement(interp, position); + Tcl_ListObjAppendElement(interp, listObj, + TkTextNewIndexObj(textPtr, &tSearch.curIndex)); } + Tcl_SetObjResult(interp, listObj); break; } } @@ -883,6 +902,7 @@ TkTextCreateTag(textPtr, tagName) tagPtr->elide = 0; tagPtr->wrapMode = TEXT_WRAPMODE_NULL; tagPtr->affectsDisplay = 0; + tagPtr->affectsDisplayGeometry = 0; textPtr->numTags++; Tcl_SetHashValue(hPtr, tagPtr); tagPtr->optionTable = Tk_CreateOptionTable(textPtr->interp, tagOptionSpecs); @@ -1132,7 +1152,7 @@ TkTextBindProc(clientData, eventPtr) # define AnyButtonMask (Button1Mask|Button2Mask|Button3Mask\ |Button4Mask|Button5Mask) - Tcl_Preserve((ClientData) textPtr); + textPtr->refCount++; /* * This code simulates grabs for mouse buttons by keeping track @@ -1197,12 +1217,16 @@ TkTextBindProc(clientData, eventPtr) oldState = eventPtr->xbutton.state; eventPtr->xbutton.state &= ~(Button1Mask|Button2Mask |Button3Mask|Button4Mask|Button5Mask); - TkTextPickCurrent(textPtr, eventPtr); + if (!(textPtr->flags & DESTROYED)) { + TkTextPickCurrent(textPtr, eventPtr); + } eventPtr->xbutton.state = oldState; } done: - Tcl_Release((ClientData) textPtr); + if (--textPtr->refCount == 0) { + ckfree((char *) textPtr); + } } /* @@ -1224,7 +1248,7 @@ TkTextBindProc(clientData, eventPtr) * then the commands associated with character entry and leave * could do just about anything. For example, the text widget * might be deleted. It is up to the caller to protect itself - * with calls to Tcl_Preserve and Tcl_Release. + * by incrementing the refCount of the text widget. * *-------------------------------------------------------------- */ diff --git a/generic/tkTextWind.c b/generic/tkTextWind.c index cc0449e..25991ac 100644 --- a/generic/tkTextWind.c +++ b/generic/tkTextWind.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextWind.c,v 1.7 2003/05/19 13:04:24 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextWind.c,v 1.8 2003/10/31 09:02:12 vincentdarley Exp $ */ #include "tk.h" @@ -59,10 +59,6 @@ static void EmbWinDelayedUnmap _ANSI_ARGS_(( ClientData clientData)); static int EmbWinDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr, int treeGone)); -static void EmbWinDisplayProc _ANSI_ARGS_(( - TkTextDispChunk *chunkPtr, int x, int y, - int lineHeight, int baseline, Display *display, - Drawable dst, int screenY)); static int EmbWinLayoutProc _ANSI_ARGS_((TkText *textPtr, TkTextIndex *indexPtr, TkTextSegment *segPtr, int offset, int maxX, int maxChars, @@ -231,6 +227,13 @@ TkTextWindowCmd(textPtr, interp, objc, objv) } } else { TkTextChanged(textPtr, &index, &index); + /* + * It's probably not true that all window configuration + * can change the line height, so we could be more + * efficient here and only call this when necessary. + */ + TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TK_TEXT_INVALIDATE_ONLY); return EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4); } break; @@ -289,10 +292,12 @@ TkTextWindowCmd(textPtr, interp, objc, objv) if (EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4) != TCL_OK) { TkTextIndex index2; - TkTextIndexForwChars(&index, 1, &index2); + TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES); TkBTreeDeleteChars(&index, &index2); return TCL_ERROR; } + TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TK_TEXT_INVALIDATE_ONLY); break; } case WIND_NAMES: { @@ -463,6 +468,8 @@ EmbWinStructureProc(clientData, eventPtr) index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } /* @@ -497,6 +504,8 @@ EmbWinRequestProc(clientData, tkwin) index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } /* @@ -542,6 +551,8 @@ EmbWinLostSlaveProc(clientData, tkwin) index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } /* @@ -763,7 +774,7 @@ EmbWinLayoutProc(textPtr, indexPtr, ewPtr, offset, maxX, maxChars, * Fill in the chunk structure. */ - chunkPtr->displayProc = EmbWinDisplayProc; + chunkPtr->displayProc = TkTextEmbWinDisplayProc; chunkPtr->undisplayProc = EmbWinUndisplayProc; chunkPtr->measureProc = (Tk_ChunkMeasureProc *) NULL; chunkPtr->bboxProc = EmbWinBboxProc; @@ -819,7 +830,7 @@ EmbWinCheckProc(ewPtr, linePtr) /* *-------------------------------------------------------------- * - * EmbWinDisplayProc -- + * TkTextEmbWinDisplayProc -- * * This procedure is invoked by the text displaying code * when it is time to actually draw an embedded window @@ -835,8 +846,9 @@ EmbWinCheckProc(ewPtr, linePtr) *-------------------------------------------------------------- */ -static void -EmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY) +void +TkTextEmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, + display, dst, screenY) TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ int x; /* X-position in dst at which to * draw this chunk (differs from @@ -848,8 +860,10 @@ EmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY) * the chunk itself). */ int lineHeight; /* 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 */ + Display *display; /* Display to use for drawing + * (unused). */ + Drawable dst; /* Pixmap or window in which to draw + * (unused). */ int screenY; /* Y-coordinate in text window that * corresponds to y. */ { |